admin - Veebilehed24

A landing page has one job: move the right visitor toward one clear action. That action can be a quote request, booking, demo request, purchase, download, signup, consultation, webinar registration, or contact form submission. A weak AI prompt usually creates a generic page with a big headline, a few benefits, and a basic “Get Started” button. A strong landing page prompt gives AI the offer, audience, traffic source, conversion goal, objections, proof, CTA, page structure, and rules about what must not be invented.

This guide gives you 30 professional AI prompts for landing pages. These prompts are built for real conversion workflows: landing page strategy, hero sections, lead generation pages, service landing pages, product landing pages, SaaS landing pages, local service landing pages, webinar pages, free download pages, quote request pages, booking pages, CTA copy, social proof sections, objection handling, FAQ sections, form copy, A/B testing ideas, and complete landing page audits.

This post focuses only on landing page prompts and conversion copy. It does not replace broader website copywriting, WordPress planning, SEO article writing, ecommerce product descriptions, or metadata work. For those tasks, use our related guides: AI prompts for website copywriting, ChatGPT prompts for WordPress websites, ChatGPT prompts for SEO content writing, AI prompts for meta titles and descriptions, and AI prompts for ecommerce product descriptions.

View All 30 AI Landing Page Prompts

What Are AI Prompts for Landing Pages?

AI prompts for landing pages are structured instructions that help AI plan and write a focused conversion page. A good landing page prompt tells AI what the offer is, who the visitor is, where the traffic comes from, what the visitor should do next, what proof is available, what objections must be answered, and what action the page should optimize for.

A weak prompt might say, “write a landing page for my service.” A stronger prompt says, “Act as a conversion copywriter. Write a landing page for a roof repair quote request campaign targeting homeowners from Google Ads. Include a clear hero section, benefits, proof, process, objections, FAQ, and a short quote request form. Do not invent prices, reviews, guarantees, or emergency response times.” The second prompt gives AI the information needed to create usable landing page copy.

Why Landing Page Prompts Matter

Landing pages are different from normal website pages. A normal website page may have several navigation paths, but a landing page should usually focus on one offer, one audience, one message, and one conversion goal. AI can help write landing pages faster, but the result depends heavily on the quality of the prompt.

How to Use These Landing Page Prompts

Replace the bracketed fields such as [offer], [target audience], [traffic source], [conversion goal], [proof], [objections], [CTA], and [tone]. Add real business details whenever possible. If you need general website copy first, use AI prompts for website copywriting. If you build landing pages in WordPress, use ChatGPT prompts for WordPress websites.

What Should a Good Landing Page Prompt Include?

Offer

Tell AI exactly what the landing page is promoting: service, product, free download, demo, quote request, booking, or event.

Traffic source

Mention whether visitors come from Google Ads, SEO, social media, email, referral, LinkedIn, Facebook, or a campaign.

Proof and objections

Include real proof and common doubts about price, trust, time, quality, complexity, risk, or fit.

Conversion action

Define the one main action: request a quote, book a call, buy, start trial, download, register, or contact.

30 AI Prompts for Landing Pages

Below are 30 professional AI prompts for landing pages. Each prompt is focused on one practical conversion task, from full landing page copy and hero sections to lead magnets, quote request pages, social proof, objection handling, form copy, A/B testing, and landing page audits.

1. Complete Landing Page Copy Prompt

This prompt creates a full landing page structure with hero copy, offer explanation, proof, objections, FAQs, form copy, and CTA sections.

Best forFull landing pages
OutputComplete conversion copy
Use whenYou need a full first draft
Copy-paste prompt

How to customize it: Add real proof, campaign source, and objections. Landing page copy becomes much stronger when the prompt matches the visitor’s intent.

2. Landing Page Hero Section Prompt

This prompt creates high-converting hero section copy for landing pages where first-screen clarity is critical.

Best forHero sections
OutputHeadline, subheadline, CTA
Use whenThe top section is weak
Copy-paste prompt

Why it works: A landing page hero section must quickly answer what the offer is, who it is for, and what happens next.

3. Lead Generation Landing Page Prompt

This prompt creates landing page copy for quote requests, consultation forms, bookings, demos, and inquiry campaigns.

Best forLead generation
OutputLead-focused page copy
Use whenYou need inquiries or calls
Copy-paste prompt

How to use it: Use this for service businesses, agencies, consultants, contractors, clinics, B2B companies, and quote request funnels.

4. Service Landing Page Prompt

This prompt creates landing page copy for one specific service, offer, campaign, or local service promotion.

Best forService offers
OutputService conversion copy
Use whenYou promote one service
Copy-paste prompt

Related: For full service page copy, use AI prompts for website copywriting.

5. Product Landing Page Prompt

This prompt creates conversion-focused landing page copy for a product, digital product, physical item, software tool, or product launch.

Best forProduct offers
OutputProduct landing copy
Use whenYou promote one product
Copy-paste prompt

Related: For ecommerce product descriptions, use AI prompts for ecommerce product descriptions.

6. SaaS Landing Page Prompt

This prompt creates SaaS landing page copy for free trials, demos, product-led growth, waitlists, and subscription tools.

Best forSaaS landing pages
OutputSoftware conversion copy
Use whenYou promote software
Copy-paste prompt

Why it works: SaaS landing pages need to connect features to outcomes and explain why signing up is worth the effort.

Need a landing page that converts better?

If your landing page has weak hero copy, unclear benefits, poor CTA text, missing proof, or a form that does not convert, we can help write and structure a stronger conversion page.

Improve My Landing Page

7. Google Ads Landing Page Prompt

This prompt creates landing page copy that matches paid search intent, ad copy, keyword intent, and conversion goals.

Best forGoogle Ads campaigns
OutputSearch-intent landing copy
Use whenPaid search traffic needs message match
Copy-paste prompt

How to use it: Paste the actual ad copy and keyword. Message match is critical for paid search landing pages.

8. Facebook Ads Landing Page Prompt

This prompt creates landing page copy for social traffic where visitors may not be actively searching yet.

Best forFacebook campaigns
OutputSocial traffic landing copy
Use whenVisitors need more context
Copy-paste prompt

Why it works: Social traffic often needs more context than search traffic because users were not always actively looking for the offer.

9. LinkedIn Landing Page Prompt

This prompt creates B2B landing page copy for LinkedIn campaigns, professional services, SaaS demos, and lead generation.

Best forB2B LinkedIn traffic
OutputProfessional conversion copy
Use whenYou target business decision makers
Copy-paste prompt

How to use it: Add the decision maker role. B2B landing pages should speak to the buyer’s responsibility and risk.

10. Email Campaign Landing Page Prompt

This prompt creates landing page copy for email campaigns where the page must continue the same message from the email.

Best forEmail campaigns
OutputEmail-matched landing copy
Use whenEmail clicks need a focused page
Copy-paste prompt

Why it works: Email landing pages should continue the same promise instead of sending users to a generic homepage.

11. Free Download Landing Page Prompt

This prompt creates landing page copy for lead magnets such as checklists, templates, guides, PDFs, ebooks, and free resources.

Best forLead magnets
OutputDownload page copy
Use whenYou offer a free resource
Copy-paste prompt

How to use it: Add exact resource details. The more specific the download is, the better the landing page will convert.

12. Webinar Registration Landing Page Prompt

This prompt creates landing page copy for webinars, workshops, live training, online events, and registration pages.

Best forWebinar registrations
OutputEvent signup copy
Use whenYou need registrations
Copy-paste prompt

Why it works: Webinar pages need to explain what people will learn and why it is worth their time.

Need a campaign landing page for ads, email, or lead magnets?

We can help write landing pages for Google Ads, social campaigns, email offers, webinars, downloads, quote requests, demos, and product launches.

Build My Landing Page

13. Quote Request Landing Page Prompt

This prompt creates landing page copy for quote request forms where visitors need clarity before submitting project details.

Best forQuote requests
OutputQuote form landing copy
Use whenYou sell custom services
Copy-paste prompt

How to use it: This works well for construction, web design, custom products, repairs, B2B services, and project-based offers.

14. Booking Landing Page Prompt

This prompt creates landing page copy for booking calls, appointments, consultations, demos, meetings, and sessions.

Best forBookings
OutputAppointment page copy
Use whenYou need scheduled actions
Copy-paste prompt

Why it works: Booking pages convert better when visitors understand what happens during the call and whether it is right for them.

15. Waitlist Landing Page Prompt

This prompt creates landing page copy for waitlists, beta launches, early access offers, and coming soon campaigns.

Best forWaitlists and launches
OutputEarly access copy
Use whenYou are collecting interest
Copy-paste prompt

How to use it: Use this before launch to test demand, collect leads, and explain what people are signing up for.

16. Landing Page Offer Positioning Prompt

This prompt helps clarify the offer before writing the full landing page, so the page message is sharper and easier to convert.

Best forOffer clarity
OutputPositioning and angles
Use whenThe offer feels unclear
Copy-paste prompt

Why it works: Landing page copy is much easier to write when the offer, audience, and strongest angle are clear before writing sections.

17. Landing Page Benefits Prompt

This prompt turns offer features into landing page benefits that are specific, scannable, and conversion-focused.

Best forBenefit sections
OutputFeature-to-benefit copy
Use whenThe page is too feature-heavy
Copy-paste prompt

How to use it: Paste raw features. The prompt turns them into benefits visitors can understand quickly.

18. Landing Page CTA Prompt

This prompt creates stronger CTA sections, button text, form headlines, and reassurance microcopy for landing pages.

Best forCTA copy
OutputCTA variations
Use whenThe page has weak action text
Copy-paste prompt

Why it works: CTA copy should explain the action and reduce risk before the visitor clicks.

Need stronger landing page CTAs and conversion copy?

We can improve your landing page offer, benefits, CTA text, lead form copy, proof sections, FAQ blocks, and overall conversion flow.

Improve My Landing Page Copy

19. Landing Page Form Copy Prompt

This prompt creates better landing page form copy, field labels, helper text, privacy notes, and confirmation messages.

Best forForms
OutputForm copy and microcopy
Use whenForms feel unclear or risky
Copy-paste prompt

Related: For UI inspiration, use modern CSS forms and JavaScript form validation examples.

20. Landing Page Social Proof Prompt

This prompt creates landing page proof sections from real reviews, case studies, experience, portfolio work, or process details.

Best forProof sections
OutputTrust and credibility copy
Use whenVisitors need trust
Copy-paste prompt

How to customize it: Add real proof. Landing pages need proof because visitors are often deciding quickly.

21. Landing Page Objection Handling Prompt

This prompt creates copy that answers visitor doubts about price, time, trust, complexity, risk, results, or fit.

Best forObjection handling
OutputFAQ and reassurance copy
Use whenVisitors hesitate before converting
Copy-paste prompt

Why it works: Landing pages often fail when they ask for action before answering the visitor’s real doubts.

22. Landing Page FAQ Prompt

This prompt creates conversion-focused FAQ sections that answer doubts and support the main landing page action.

Best forFAQ sections
OutputLanding page FAQs
Use whenVisitors need final answers
Copy-paste prompt

How to use it: Add real sales questions, support questions, objections, and call notes for stronger FAQ content.

23. Landing Page A/B Testing Prompt

This prompt creates test ideas for headlines, CTAs, offers, forms, proof sections, page length, and messaging angles.

Best forA/B testing
OutputTest hypotheses and variants
Use whenYou want better conversion rate
Copy-paste prompt

Why it works: A/B testing works best when every test has a clear reason, a measurable goal, and only one major variable changed at a time.

24. Landing Page Audit Prompt

This prompt audits an existing landing page and finds weak messaging, missing proof, unclear CTAs, form friction, and conversion gaps.

Best forLanding page audits
OutputConversion improvement plan
Use whenThe page exists but underperforms
Copy-paste prompt

How to use it: Paste your page copy and traffic source. The traffic source matters because the page must match visitor intent.

Need a landing page audit?

If your landing page gets traffic but not enough inquiries, bookings, demos, signups, or purchases, we can review the copy, CTA flow, form, proof sections, and conversion structure.

Audit My Landing Page

25. Local Service Landing Page Prompt

This prompt creates landing page copy for local service campaigns, city-specific offers, quote requests, and appointment pages.

Best forLocal service campaigns
OutputLocal landing page copy
Use whenYou target a local area
Copy-paste prompt

How to use it: Use this only for real service areas. Local landing pages should not fake local presence.

26. Ecommerce Promotion Landing Page Prompt

This prompt creates landing page copy for ecommerce sales, product collections, seasonal offers, bundles, and campaign pages.

Best forEcommerce campaigns
OutputPromotion page copy
Use whenYou promote products or bundles
Copy-paste prompt

Related: For individual product copy, use AI prompts for ecommerce product descriptions.

27. Landing Page Thank-You Page Prompt

This prompt creates thank-you page copy after a signup, quote request, download, demo request, purchase, or registration.

Best forPost-conversion pages
OutputThank-you page copy
Use whenYou need better post-submit flow
Copy-paste prompt

Why it works: A thank-you page should confirm the action, explain what happens next, and keep the visitor engaged.

28. Landing Page Follow-Up Email Prompt

This prompt creates follow-up emails that continue the landing page message after a form submission, download, registration, or demo request.

Best forLead follow-up
OutputEmail sequence copy
Use whenYou need post-conversion follow-up
Copy-paste prompt

How to use it: Landing page conversion does not end at the form. Follow-up copy helps turn leads into customers.

29. Landing Page SEO Metadata Prompt

This prompt creates SEO title, meta description, focus keyword, social title, social description, and image SEO text for landing pages.

Best forSEO and social snippets
OutputMetadata settings
Use whenThe landing page will be indexed or shared
Copy-paste prompt

Related: For more SEO snippet examples, use AI prompts for meta titles and descriptions.

30. Complete Landing Page Workflow Prompt

This prompt creates a full AI-assisted landing page workflow from offer strategy to copywriting, form copy, testing, and follow-up.

Best forFull landing page workflow
OutputStep-by-step conversion system
Use whenYou build landing pages often
Copy-paste prompt

Why it works: A complete landing page system includes strategy, copy, form flow, proof, follow-up, and testing — not only one headline and one button.

Need a complete landing page system?

If you need a landing page for ads, SEO, email, lead generation, demos, webinars, downloads, or quote requests, we can help with strategy, copy, design direction, forms, CTAs, and conversion flow.

Contact Us

Landing Page Prompt Best Practices

AI can help write landing page copy faster, but landing pages need more precision than normal website pages. A good landing page prompt should include the offer, audience, traffic source, conversion goal, proof, objections, CTA, and information that must not be invented. This helps AI create a page that is focused, believable, and easier to test.

Common Landing Page AI Mistakes

The most common mistake is asking AI to write a landing page without explaining the traffic source and conversion goal. A landing page for cold Facebook traffic should not be written the same way as a landing page for high-intent Google Ads traffic. The page has to match the visitor’s awareness level and the promise that brought them there.

AI Landing Page Prompts FAQ

Can AI write landing pages?

Yes, AI can write landing pages when the prompt includes the offer, audience, traffic source, conversion goal, proof, objections, CTA, and clear rules about not inventing claims.

What is the best AI prompt for landing pages?

The best AI landing page prompt gives AI a clear role, one offer, one target audience, one conversion goal, the traffic source, proof, objections, tone, and the desired page structure.

Can AI write landing pages for Google Ads?

Yes. For Google Ads landing pages, include the keyword, ad headline, ad description, offer, audience, and conversion action so the page matches paid search intent.

Can AI improve landing page conversions?

AI can suggest stronger copy, better CTAs, objection handling, form improvements, proof sections, FAQ ideas, and A/B testing ideas. Real conversion improvement still needs traffic data and testing.

Should landing pages be SEO optimized?

Some landing pages should be indexed and SEO optimized, especially evergreen service or product pages. Short paid-ad-only pages may sometimes be better as noindex pages, depending on strategy.

What should I include in a landing page prompt?

Include the offer, traffic source, target audience, visitor problem, desired outcome, conversion action, proof, objections, CTA, tone, and any claims AI must not invent.

Conclusion

AI can help create landing pages faster, but the quality depends on the prompt. A strong landing page prompt gives AI the offer, target audience, traffic source, conversion goal, proof, objections, CTA, and rules about what must not be invented. This creates copy that is more focused, more believable, and easier to test.

Use these 30 AI prompts as a complete landing page workflow. Start with offer positioning, write hero copy, create benefit sections, add proof, answer objections, improve form copy, build FAQs, write thank-you page copy, and plan A/B tests. For related workflows, use AI prompts for website copywriting, ChatGPT prompts for WordPress websites, and AI prompts for meta titles and descriptions.

WordPress websites need more than a nice design. A good WordPress site needs clear content, strong SEO structure, useful service pages, optimized product pages, fast loading, clean navigation, better internal links, conversion-focused calls to action, reliable plugins, and regular improvements. ChatGPT can help with all of this, but only when the prompt gives enough context about the website, audience, goal, page type, SEO keyword, business model, and technical limits.

This guide gives you 30 professional ChatGPT prompts for WordPress websites. These prompts are built for real WordPress workflows: website content planning, homepage copy, service pages, blog ideas, SEO improvements, internal linking, WooCommerce product pages, plugin planning, UX audits, speed optimization ideas, schema planning, FAQ creation, navigation improvements, and conversion-focused website updates.

This post focuses only on WordPress website improvements. It does not replace deeper topic-specific guides. For related workflows, use our guides about ChatGPT prompts for SEO content writing, ChatGPT prompts for keyword research, AI prompts for meta titles and descriptions, AI prompts for social media posts, and AI prompts for ecommerce product descriptions.

View All 30 ChatGPT WordPress Prompts

What Are ChatGPT Prompts for WordPress Websites?

ChatGPT prompts for WordPress websites are structured instructions that help AI produce useful website content, SEO recommendations, plugin ideas, page improvements, UX suggestions, and technical planning for WordPress projects. A good prompt tells ChatGPT what type of WordPress website you have, who the site is for, what page or feature you want to improve, what goal you want to achieve, and what output format you need.

A weak prompt might say, “improve my WordPress website.” A strong prompt says, “Act as a WordPress SEO and UX consultant. Review this homepage structure for a local service business, suggest content sections, internal links, CTA improvements, FAQ ideas, schema opportunities, and practical changes that can be implemented in WordPress without changing the whole theme.” The second prompt gives AI a real task and a usable output.

Why WordPress Prompts Matter

WordPress is flexible, but that also means many websites become messy over time. Pages get added without structure, plugins are installed without a clear reason, blog posts do not link to service pages, product pages miss useful FAQs, and SEO settings are often copied from the first paragraph. ChatGPT can help organize these problems when your prompt gives it clear context.

How to Use These WordPress Prompts

Replace the bracketed parts such as [website type], [target audience], [page URL], [main goal], [primary keyword], [current issue], and [plugins used]. Add real information from your WordPress site whenever possible. If you are improving SEO content, use keyword data from ChatGPT prompts for keyword research. If you need product descriptions, use AI prompts for ecommerce product descriptions.

What Should a Good WordPress Prompt Include?

Website type

Tell ChatGPT whether the site is a service business, blog, WooCommerce store, SaaS site, portfolio, or landing page.

Page goal

Explain whether the page should generate leads, sell products, improve SEO, build trust, or support users.

WordPress context

Mention the theme, builder, plugins, post type, WooCommerce setup, or technical limitation when relevant.

Output format

Ask for a checklist, table, Gutenberg structure, SEO metadata, content outline, audit, or implementation plan.

30 ChatGPT Prompts for WordPress Websites

Below are 30 professional ChatGPT prompts for WordPress websites. Each prompt is focused on one practical website task, from improving content and SEO to planning plugins, auditing UX, building service pages, improving WooCommerce, and creating a better website improvement workflow.

1. WordPress Website Audit Prompt

This prompt helps review a WordPress website from content, SEO, UX, conversion, and technical improvement angles.

Best forFull website audits
OutputAudit checklist and priorities
Use whenYou want a clear improvement plan
Copy-paste prompt

How to customize it: Add page URLs, plugin list, and current problems. This makes the audit much more specific.

2. WordPress Homepage Improvement Prompt

This prompt improves homepage structure, messaging, CTA placement, trust signals, and SEO sections.

Best forHomepage improvements
OutputHomepage section plan
Use whenThe homepage is unclear
Copy-paste prompt

Why it works: WordPress homepages often fail because they look nice but do not explain the offer quickly enough.

3. WordPress Service Page Prompt

This prompt creates or improves a WordPress service page for local businesses, agencies, consultants, and professional services.

Best forService pages
OutputService page structure
Use whenYou need leads or quote requests
Copy-paste prompt

How to use it: Use this for roofing, web design, SEO, legal, construction, consulting, repair, beauty, health, or any service page.

4. WordPress Blog Topic Plan Prompt

This prompt creates blog ideas that support SEO and do not directly compete with existing posts.

Best forBlog planning
OutputNon-competing blog topic list
Use whenYou want more SEO content
Copy-paste prompt

Why it works: This prompt is useful when you want content growth without creating competing WordPress posts.

5. WordPress Internal Linking Prompt

This prompt helps connect WordPress pages and posts with useful internal links.

Best forInternal linking
OutputInternal link map
Use whenYou want better SEO structure
Copy-paste prompt

How to use it: Paste your real URLs. Internal links work best when they connect pages by search intent and user journey.

6. WordPress SEO Metadata Prompt

This prompt creates SEO title, meta description, focus keyword, secondary keywords, social metadata, and image SEO for WordPress pages.

Best forSEO plugin settings
OutputMetadata and keywords
Use whenYou finish a page or post
Copy-paste prompt

Why it works: WordPress SEO plugins often pull the first paragraph. This prompt creates cleaner snippet-ready metadata.

Need a better WordPress content and SEO structure?

If your WordPress website has weak pages, missing internal links, unclear CTAs, or SEO content gaps, we can help improve the structure, content, metadata, and conversion path.

Improve My WordPress Website

7. WordPress FAQ Section Prompt

This prompt creates useful FAQ sections for service pages, product pages, blog posts, and landing pages.

Best forFAQ sections
OutputQuestions and answers
Use whenYou need stronger page usefulness
Copy-paste prompt

How to use it: Add real customer questions when possible. FAQs should answer doubts, not just fill space.

8. WordPress Plugin Planning Prompt

This prompt helps decide whether a feature should use a plugin, custom code, theme settings, or a simpler content solution.

Best forPlugin decisions
OutputImplementation recommendation
Use whenYou are unsure what to install
Copy-paste prompt

Why it works: WordPress sites often become slow because every small issue is solved with another plugin.

9. WordPress Speed Improvement Prompt

This prompt creates a practical WordPress speed improvement checklist without guessing server access or plugin settings.

Best forPerformance planning
OutputSpeed checklist
Use whenThe site feels slow
Copy-paste prompt

How to use it: Paste PageSpeed recommendations for a more specific result.

10. WordPress Navigation Improvement Prompt

This prompt improves menus, header links, footer links, category structure, and user path clarity.

Best forMenus and navigation
OutputNavigation structure
Use whenUsers cannot find pages
Copy-paste prompt

Why it works: Better WordPress navigation helps SEO, UX, and conversions at the same time.

11. WordPress Content Gap Prompt

This prompt finds missing pages, sections, topics, FAQs, and internal links on a WordPress website.

Best forContent gaps
OutputMissing content list
Use whenThe site feels incomplete
Copy-paste prompt

How to use it: Paste the real page and post list. This makes content gap analysis much more accurate.

12. WordPress WooCommerce Improvement Prompt

This prompt reviews WooCommerce product pages, category pages, checkout flow, trust signals, and ecommerce SEO.

Best forWooCommerce stores
OutputStore improvement plan
Use whenYou want better product sales or inquiries
Copy-paste prompt

Related: For product copy itself, use AI prompts for ecommerce product descriptions.

Want a cleaner WordPress site that converts better?

We can help improve homepage structure, service pages, WooCommerce pages, menus, internal links, metadata, and technical WordPress issues without filling your site with unnecessary plugins.

Get WordPress Help

13. WordPress Schema Planning Prompt

This prompt helps plan structured data opportunities for WordPress pages, posts, products, FAQs, local businesses, and services.

Best forStructured data planning
OutputSchema opportunity list
Use whenYou want AI-readable SEO structure
Copy-paste prompt

Why it works: Schema should describe real page content. It should not be used to fake information that is not visible to users.

14. WordPress Gutenberg Layout Prompt

This prompt creates a Gutenberg-friendly page structure with sections, headings, copy notes, CTA blocks, and FAQ ideas.

Best forGutenberg pages
OutputBlock layout plan
Use whenYou build pages in the block editor
Copy-paste prompt

How to use it: Use this before building pages in WordPress so the content and structure are planned first.

15. WordPress Landing Page Prompt

This prompt creates a conversion-focused WordPress landing page plan for ads, campaigns, services, or offers.

Best forLanding pages
OutputConversion page structure
Use whenYou need leads or sales
Copy-paste prompt

Related: For deeper landing page copy, use AI prompts for landing pages.

16. WordPress About Page Prompt

This prompt helps create a stronger WordPress About page that builds trust, explains the business, and supports conversions.

Best forAbout pages
OutputTrust-building page copy
Use whenYour About page is too generic
Copy-paste prompt

Why it works: A strong About page should build trust and guide visitors toward the next step, not only tell a generic company story.

17. WordPress Contact Page Prompt

This prompt improves a WordPress Contact page so users know how to reach you, what to expect, and what information to send.

Best forContact pages
OutputContact page structure
Use whenYou want more useful inquiries
Copy-paste prompt

How to use it: Use this when your contact page only has a form and no explanation, trust text, or user guidance.

18. WordPress CTA Improvement Prompt

This prompt improves calls to action across WordPress pages, buttons, forms, banners, popups, and service sections.

Best forCTA optimization
OutputCTA copy and placement ideas
Use whenVisitors do not take action
Copy-paste prompt

Why it works: Many WordPress websites have weak CTA text like “Submit” or “Learn More.” Strong CTAs explain the next step clearly.

Need WordPress pages that actually bring inquiries?

We can help improve your homepage, About page, Contact page, service pages, CTAs, internal links, and SEO metadata so your WordPress website works better as a business tool.

Improve My WordPress Pages

19. WordPress Blog Post Update Prompt

This prompt helps update old WordPress blog posts with better structure, internal links, FAQs, metadata, and fresh usefulness.

Best forOld blog updates
OutputRefresh plan
Use whenOld posts lose traffic or feel thin
Copy-paste prompt

Related: For full article creation, use ChatGPT prompts for SEO content writing.

20. WordPress Category Page Prompt

This prompt improves WordPress category pages for blogs, WooCommerce categories, resource hubs, and topic archives.

Best forCategory pages
OutputCategory SEO structure
Use whenArchive pages are thin
Copy-paste prompt

Why it works: WordPress category pages often rank better when they explain the topic and guide users to the best posts or products.

21. WordPress Tag Cleanup Prompt

This prompt helps review WordPress tags, remove useless tag clutter, and create a cleaner taxonomy structure.

Best forTag cleanup
OutputTaxonomy cleanup plan
Use whenYou have too many weak tags
Copy-paste prompt

How to use it: This is useful when tags were added randomly and do not help users or SEO.

22. WordPress Image SEO Prompt

This prompt creates better file names, alt text, image titles, and image placement notes for WordPress media.

Best forImage SEO
OutputAlt text and image notes
Use whenYou upload featured images
Copy-paste prompt

Why it works: WordPress image SEO should help accessibility and context, not just repeat keywords.

23. WordPress Local SEO Page Prompt

This prompt creates a local SEO page plan for service areas, city pages, location pages, and local business websites.

Best forLocal SEO pages
OutputLocation page structure
Use whenYou target local searches
Copy-paste prompt

How to use it: Use this for real service areas only. Thin duplicate city pages can hurt quality.

24. WordPress Website Redesign Prompt

This prompt helps plan a WordPress redesign without losing important SEO pages, content, internal links, and conversion elements.

Best forRedesign planning
OutputRedesign checklist
Use whenYou rebuild a WordPress site
Copy-paste prompt

Why it works: A WordPress redesign can damage SEO when URLs, metadata, content, headings, and internal links are changed without a plan.

Planning a WordPress redesign or SEO cleanup?

We can help plan your WordPress redesign, protect important SEO pages, improve content structure, clean up tags and categories, and make the new website easier to use.

Plan My WordPress Redesign

25. WordPress Security Checklist Prompt

This prompt creates a basic WordPress security checklist for site owners, admins, and small businesses.

Best forSecurity planning
OutputSecurity checklist
Use whenYou want safer WordPress maintenance
Copy-paste prompt

How to use it: This is a defensive checklist prompt for better maintenance, not a replacement for professional security work.

26. WordPress Accessibility Prompt

This prompt helps review a WordPress page for readability, headings, buttons, forms, images, contrast, and keyboard usability.

Best forAccessibility improvements
OutputAccessibility checklist
Use whenYou want a more usable site
Copy-paste prompt

Why it works: Accessibility improvements often also improve UX, readability, and conversions.

27. WordPress Form Improvement Prompt

This prompt improves WordPress contact forms, quote forms, lead forms, booking forms, and checkout-related forms.

Best forForms and leads
OutputForm improvement plan
Use whenForms get weak leads
Copy-paste prompt

Related: For UI examples, use modern CSS forms and JavaScript form validation examples.

28. WordPress Portfolio Page Prompt

This prompt helps create stronger portfolio, case study, project, or client work pages for WordPress websites.

Best forPortfolio pages
OutputCase study structure
Use whenYou need better proof
Copy-paste prompt

Why it works: Portfolio pages should prove capability and guide similar clients toward contacting you.

29. WordPress Maintenance Plan Prompt

This prompt creates a practical WordPress maintenance workflow for updates, backups, SEO checks, content improvements, and testing.

Best forMaintenance workflows
OutputMonthly checklist
Use whenYou want consistent site care
Copy-paste prompt

How to use it: This helps avoid the common WordPress problem where sites are published and then never maintained properly.

30. Complete WordPress Website Improvement Workflow Prompt

This prompt creates a full AI-assisted workflow for improving a WordPress website step by step.

Best forFull website improvement
OutputComplete workflow
Use whenYou want a repeatable process
Copy-paste prompt

Why it works: WordPress improvements work best as a system: audit, content, SEO, UX, conversion, speed, internal links, and maintenance.

Need a full WordPress improvement plan?

If you want a clearer WordPress website, better SEO structure, stronger service pages, cleaner internal linking, faster pages, and better conversion flow, we can help plan and build the improvements.

Contact Us

WordPress Prompt Best Practices

ChatGPT can help with WordPress websites, but the quality of the result depends on the context you provide. A strong WordPress prompt should include the website type, target audience, page goal, current problem, WordPress setup, SEO keyword, and expected output format. This helps ChatGPT create useful recommendations instead of generic advice.

Common WordPress AI Prompt Mistakes

The biggest mistake is asking ChatGPT to improve a WordPress website without giving enough detail. WordPress websites can be blogs, service sites, WooCommerce stores, membership websites, portfolios, or landing pages. Each type needs a different structure, content plan, plugin strategy, and SEO approach.

ChatGPT WordPress Prompts FAQ

Can ChatGPT help improve a WordPress website?

Yes. ChatGPT can help plan content, SEO, metadata, service pages, blog topics, internal links, plugin decisions, UX improvements, and maintenance workflows. The prompt must include real website context.

What is the best ChatGPT prompt for WordPress?

The best prompt depends on the task. A good WordPress prompt includes the website type, page goal, audience, current problem, WordPress setup, SEO keyword, and requested output format.

Can ChatGPT write WordPress pages?

Yes. ChatGPT can draft homepage sections, service pages, About pages, Contact pages, FAQs, landing pages, and blog posts. The final content should be reviewed for accuracy and brand voice.

Can ChatGPT choose WordPress plugins?

ChatGPT can help compare plugin, custom code, theme setting, and no-code solutions. You should still check plugin quality, updates, reviews, compatibility, and performance before installing anything.

Can ChatGPT help with WordPress SEO?

Yes. ChatGPT can help with keyword mapping, metadata, heading structure, internal links, FAQ ideas, schema planning, content gaps, and page improvement plans. Important SEO data should still be verified with tools.

Should I let ChatGPT make technical WordPress changes directly?

No. ChatGPT can suggest technical steps, but code, plugin, theme, database, redirect, and performance changes should be tested on staging and reviewed by someone who understands WordPress.

Conclusion

ChatGPT can be very useful for WordPress websites when you use it with clear, specific prompts. Instead of asking for generic website advice, give ChatGPT the website type, page goal, audience, SEO keyword, current problem, WordPress setup, and expected output format. This helps AI produce practical recommendations that can actually be implemented in WordPress.

Use these 30 ChatGPT prompts as a WordPress improvement workflow. Start with a website audit, then improve homepage structure, service pages, metadata, internal links, FAQs, WooCommerce pages, navigation, forms, speed, accessibility, and maintenance. For deeper content and SEO work, connect this guide with ChatGPT prompts for SEO content writing, ChatGPT prompts for keyword research, AI prompts for meta titles and descriptions, and AI prompts for ecommerce product descriptions.

Image SEO

Featured image alt text: Futuristic WordPress AI dashboard with website audit panel, SEO metadata editor, plugin planner, Gutenberg layout blocks, WooCommerce cards, performance checklist, and neon UI design.

Featured image title: ChatGPT Prompts for WordPress Websites

SEO Metadata

SEO title: 30 ChatGPT Prompts for WordPress Websites

Meta description: Use 30 ChatGPT prompts for WordPress websites, SEO, content, plugins, WooCommerce, service pages, speed, UX, and website improvements.

Focus keyword: ChatGPT prompts for WordPress websites

Secondary keywords: WordPress ChatGPT prompts, AI prompts for WordPress, WordPress SEO prompts, WordPress content prompts, WordPress plugin prompts, WooCommerce prompts, WordPress website improvement prompts, ChatGPT prompts for website content, WordPress audit prompts, WordPress UX prompts

Ecommerce product descriptions are not only short texts under a product image. A strong product description helps the customer understand what the product is, who it is for, why it matters, what problem it solves, and why it is worth buying from your store. A weak AI prompt usually creates generic product copy with empty phrases like “high quality,” “perfect for everyone,” and “must-have product.” A strong ecommerce prompt gives AI the product details, target buyer, search intent, product benefits, objections, tone, SEO keyword, and page goal.

This guide gives you 30 professional AI prompts for ecommerce product descriptions. These prompts are built for real online store workflows: product page descriptions, WooCommerce product copy, Shopify descriptions, category descriptions, short product summaries, SEO product text, benefit-focused copy, feature-to-benefit conversion, comparison copy, luxury product copy, technical product descriptions, FAQ sections, product titles, meta descriptions, and conversion-focused sales text.

This post focuses only on ecommerce product descriptions and product page copy. It does not replace keyword research, full SEO article writing, social media promotion, or landing page copy. For those tasks, use our related guides: ChatGPT prompts for keyword research, ChatGPT prompts for SEO content writing, AI prompts for meta titles and descriptions, and AI prompts for social media posts.

View All 30 AI Ecommerce Product Description Prompts

What Are AI Prompts for Ecommerce Product Descriptions?

AI prompts for ecommerce product descriptions are structured instructions that help AI write better product copy for online stores. A good ecommerce prompt gives AI the product name, product category, target customer, main use case, important features, real benefits, materials, specifications, objections, tone, SEO keyword, and conversion goal.

A basic prompt might say, “write a product description for this product.” A professional prompt says, “Act as an ecommerce conversion copywriter, write a product description for this product, explain who it is for, turn features into buyer benefits, include SEO keywords naturally, answer objections, and avoid unsupported claims.” The second prompt gives AI the information it needs to create useful product page copy.

These prompts are useful for WooCommerce stores, Shopify stores, product page writers, ecommerce SEO specialists, online shop owners, dropshipping stores, digital product sellers, product marketers, and agencies that need product descriptions that are clearer, more useful, and better aligned with search intent.

Why Product Description Prompts Matter

Product description prompts matter because ecommerce copy has to do more than sound nice. It must help the buyer understand the product, compare options, answer doubts, and take the next step. If the product copy is too generic, the customer may not understand why the product is right for them.

How to Use These Ecommerce Prompts

Each prompt below is copy-paste ready. Replace the bracketed parts such as

, [category], [target buyer], [features], [benefits], [primary keyword], [tone], and [store positioning]. The more accurate product information you provide, the better the description will be.

Use these prompts after you understand the product and customer. If you need keyword ideas first, use ChatGPT prompts for keyword research. If you need page-level metadata after the product description, use AI prompts for meta titles and descriptions. If you need to promote products on social media, use AI prompts for social media posts.

What Should a Good Product Description Prompt Include?

Product facts

Give AI real product details: size, material, specifications, features, included items, and limitations.

Target buyer

Explain who the product is for, what they care about, and what problem they are trying to solve.

SEO keyword

Add the primary keyword and related terms so AI can write naturally around ecommerce search intent.

Conversion goal

Tell AI whether the goal is add to cart, quote request, product comparison, lead, download, or inquiry.

30 AI Prompts for Ecommerce Product Descriptions

Below are 30 professional AI prompts for ecommerce product descriptions. Each prompt is focused on one specific product copywriting task, from writing a full product page description to creating short summaries, SEO copy, FAQ sections, category descriptions, and conversion-focused sales text.

1. Complete Product Description Prompt

This prompt creates a full ecommerce product description with features, benefits, use cases, objections, and SEO focus.

Best forFull product pages
OutputComplete product copy
Use whenYou need a strong first draft
Copy-paste prompt

How to customize it: Add exact product specifications and real benefits. The better the product facts, the stronger the output.

2. Short Product Description Prompt

This prompt creates a short product summary for WooCommerce short descriptions, product cards, collection pages, or quick product previews.

Best forShort descriptions
OutputConcise product copy
Use whenYou need a quick product summary
Copy-paste prompt

Why it works: Short descriptions need clarity fast. This prompt creates different lengths for different store layouts.

3. Feature-to-Benefit Product Copy Prompt

This prompt converts product features into buyer benefits. It is useful when product information is too technical or too feature-heavy.

Best forBenefit-driven copy
OutputFeature and benefit table
Use whenProduct text feels too technical
Copy-paste prompt

How to use it: Paste raw product specs. AI will help translate them into benefits customers understand.

4. SEO Product Description Prompt

This prompt creates product copy that uses ecommerce keywords naturally without stuffing the page.

Best forSEO product descriptions
OutputKeyword-aligned product copy
Use whenYou target product search traffic
Copy-paste prompt

Why it works: Ecommerce SEO copy should match buyer intent and product facts, not repeat keywords unnaturally.

5. WooCommerce Product Description Prompt

This prompt is built for WooCommerce product pages, including short description, long description, tabs, and FAQ sections.

Best forWooCommerce stores
OutputWooCommerce-ready copy
Use whenYou publish products in WordPress
Copy-paste prompt

How to customize it: Add your real product specs and shipping notes. Do not let AI guess operational details.

6. Shopify Product Description Prompt

This prompt creates Shopify-friendly product descriptions with a clear structure, benefit bullets, and buyer-focused copy.

Best forShopify product pages
OutputShopify-ready copy
Use whenYou publish Shopify products
Copy-paste prompt

Why it works: Shopify pages often need clean, scannable copy that helps buyers quickly understand product value.

Need better ecommerce product descriptions?

If your product pages are too generic, too short, or not built for SEO and conversions, we can help improve WooCommerce product copy, category text, product FAQs, and metadata.

Contact Us

7. Product Title Optimization Prompt

This prompt creates clearer product titles for ecommerce search, product grids, category pages, and SEO snippets.

Best forProduct titles
OutputOptimized title options
Use whenTitles are vague or too long
Copy-paste prompt

How to use it: Use this when product names are too vague, too technical, or missing important buyer attributes.

8. Product Bullet Points Prompt

This prompt creates clear ecommerce bullet points for product pages, product cards, Amazon-style listings, and comparison sections.

Best forProduct bullets
OutputBenefit bullets
Use whenYou need scannable copy
Copy-paste prompt

Why it works: Product bullets help buyers scan important details before reading the full description.

9. Product FAQ Prompt

This prompt creates product FAQ questions and answers that help customers make a decision and reduce support questions.

Best forProduct FAQs
OutputFAQ section
Use whenCustomers ask repeated questions
Copy-paste prompt

How to customize it: Add real customer questions from support emails, live chat, reviews, or Search Console queries.

10. Product Objection Handling Prompt

This prompt helps answer buyer doubts directly, such as price, fit, quality, size, compatibility, setup, or use case concerns.

Best forBuyer objections
OutputObjection answers
Use whenCustomers hesitate before buying
Copy-paste prompt

Why it works: Better product copy often comes from answering what buyers are worried about, not only listing features.

11. Product Comparison Copy Prompt

This prompt creates comparison copy for helping buyers choose between similar products, models, sizes, or packages.

Best forProduct comparison
OutputComparison table text
Use whenYou sell similar products
Copy-paste prompt

How to use it: Use this for product families, size variants, bundles, materials, packages, or similar models.

12. Product Category Description Prompt

This prompt creates ecommerce category descriptions for SEO, product browsing, and buyer education.

Best forCategory pages
OutputCategory SEO copy
Use whenYou optimize collection pages
Copy-paste prompt

Why it works: Category pages need SEO text, but they must still help buyers choose the right product group.

Need ecommerce category and product copy that helps buyers?

We can help create better product descriptions, category text, FAQ sections, SEO snippets, and internal linking for WooCommerce and other ecommerce websites.

Improve My Product Pages

13. Technical Product Description Prompt

This prompt creates product descriptions for technical products where accuracy, specifications, and buyer clarity are important.

Best forTechnical products
OutputTechnical but readable copy
Use whenSpecs matter
Copy-paste prompt

How to customize it: Add complete technical data. AI should explain the specs, not guess them.

14. Luxury Product Description Prompt

This prompt creates premium product copy without overusing empty luxury words or unsupported claims.

Best forPremium products
OutputLuxury-style product copy
Use whenThe brand is premium
Copy-paste prompt

Why it works: Premium copy should feel specific and refined, not overloaded with generic luxury adjectives.

15. Budget Product Description Prompt

This prompt creates product copy for affordable products without making the product sound cheap or low value.

Best forAffordable products
OutputValue-focused copy
Use whenPrice is part of the appeal
Copy-paste prompt

How to use it: This works well for entry-level, budget-friendly, starter, or practical everyday products.

16. Digital Product Description Prompt

This prompt creates product copy for digital downloads, templates, plugins, courses, files, tools, and online resources.

Best forDigital products
OutputDigital product sales copy
Use whenYou sell downloads or tools
Copy-paste prompt

Why it works: Digital products need clear expectations: what the buyer gets, how it works, and who it is for.

17. Product Bundle Description Prompt

This prompt creates ecommerce copy for product bundles, kits, packages, sets, starter packs, and combined offers.

Best forBundles and kits
OutputBundle sales copy
Use whenYou sell product packages
Copy-paste prompt

How to customize it: Add the exact items in the bundle. Never let AI guess what is included.

18. Product Storytelling Prompt

This prompt creates a product story that explains the problem, use case, and product value in a more engaging way.

Best forStory-driven products
OutputProduct story copy
Use whenThe product needs emotional context
Copy-paste prompt

Why it works: Some products need context. Storytelling helps buyers imagine how the product fits their life or work.

Need product pages that explain value clearly?

We can help write or improve product pages, product bundles, digital product descriptions, product comparison sections, and FAQ blocks that support SEO and conversions.

Fix My Product Copy

19. Product Description Rewrite Prompt

This prompt improves an existing product description by making it clearer, more useful, and more conversion-focused.

Best forRewriting weak copy
OutputImproved product copy
Use whenCurrent description is generic
Copy-paste prompt

How to use it: Paste your current text. This is useful for updating old product pages that were copied from supplier descriptions.

20. Duplicate Product Description Prompt

This prompt helps rewrite similar supplier descriptions into unique, buyer-focused ecommerce copy.

Best forDuplicate copy issues
OutputUnique product copy
Use whenSupplier text is duplicated
Copy-paste prompt

Why it works: Many ecommerce stores reuse supplier text. This prompt helps create more original and useful copy.

21. Product Page Conversion Audit Prompt

This prompt reviews a product page and identifies what is missing from the copy before you publish or update it.

Best forProduct page audits
OutputCopy improvement checklist
Use whenYou want better conversion
Copy-paste prompt

How to use it: Use this before publishing important product pages or when product pages get traffic but weak sales.

22. Product Page SEO Metadata Prompt

This prompt creates SEO title, meta description, focus keyword, secondary keywords, and image alt text for product pages.

Best forProduct metadata
OutputSEO plugin settings
Use whenYou finish a product page
Copy-paste prompt

Why it works: Product metadata should match the product page, not reuse the category description or first paragraph.

23. Product Image Alt Text Prompt

This prompt creates accurate image alt text for product photos, product galleries, lifestyle images, and detail shots.

Best forImage SEO
OutputAlt text options
Use whenYou upload product images
Copy-paste prompt

How to customize it: Describe the image accurately. Alt text should help users understand the image, not only SEO.

24. Product Review Summary Prompt

This prompt summarizes real product reviews into useful product page insights without inventing testimonials.

Best forReview insights
OutputReview summary copy
Use whenYou have customer reviews
Copy-paste prompt

Why it works: Real customer language can improve product descriptions and help answer buyer questions more naturally.

Want stronger SEO metadata and product page structure?

We can help optimize ecommerce SEO titles, meta descriptions, image alt text, product FAQs, review summaries, and product page content for better clarity and search performance.

Optimize My Ecommerce SEO

25. Product Email Copy Prompt

This prompt turns product information into promotional email copy for ecommerce campaigns, product launches, and product recommendations.

Best forEmail product promotion
OutputEmail subject and body copy
Use whenYou promote products by email
Copy-paste prompt

How to use it: Use this for product launches, abandoned browse campaigns, product recommendation emails, and seasonal promotions.

26. Product Social Media Copy Prompt

This prompt creates platform-specific social media posts from product page details.

Best forProduct social posts
OutputFacebook, LinkedIn, Instagram, X copy
Use whenYou promote products socially
Copy-paste prompt

Why it works: Product page copy and social media copy should connect, but social posts need platform-specific wording.

27. Product Page Internal Linking Prompt

This prompt helps plan internal links from product pages to categories, buying guides, comparison posts, and related products.

Best forInternal linking
OutputProduct link map
Use whenYou want better ecommerce structure
Copy-paste prompt

How to use it: Use this to connect products with categories, guides, and related buying decisions.

28. Product Page Content Gap Prompt

This prompt identifies missing product page content, such as specifications, benefits, FAQ answers, objections, and comparison details.

Best forContent gaps
OutputMissing content checklist
Use whenProduct page feels incomplete
Copy-paste prompt

Why it works: Many product pages fail because they do not answer enough buyer questions before the purchase decision.

29. Bulk Product Description Prompt

This prompt helps create product descriptions for multiple products while keeping each product unique and specific.

Best forBulk product copy
OutputProduct copy table
Use whenYou update many products
Copy-paste prompt

How to use it: This is useful for ecommerce stores with many products, but every output should still be checked manually.

30. Complete Ecommerce Product Copy Workflow Prompt

This prompt creates a full repeatable AI workflow for product descriptions, SEO metadata, FAQs, internal links, and product promotion.

Best forFull ecommerce workflow
OutputRepeatable product copy system
Use whenYou want a product content process
Copy-paste prompt

Why it works: Ecommerce copy works better when product data, SEO, benefits, FAQs, metadata, and promotion are part of one workflow.

Need a full ecommerce product copy system?

If you want better product descriptions, WooCommerce product pages, category descriptions, product SEO metadata, FAQs, and internal linking, we can help build a clear ecommerce content workflow.

Contact Us

Ecommerce Product Description Best Practices

AI can help write ecommerce product descriptions faster, but product copy still needs real product information and human review. A good product description should explain the product clearly, translate features into buyer benefits, answer objections, include useful details, and support the page’s SEO keyword naturally.

Common AI Product Description Mistakes

The biggest mistake is asking AI to write product descriptions without giving real product data. When AI does not know the product, it fills the copy with generic phrases and may invent details. Ecommerce copy must stay accurate because customers use it to make buying decisions.

AI Ecommerce Product Description FAQ

Can AI write ecommerce product descriptions?

Yes, AI can write ecommerce product descriptions when you provide real product details, target buyer information, SEO keywords, product benefits, and clear quality rules. Human review is still important before publishing.

What is the best AI prompt for product descriptions?

The best prompt gives AI the product name, category, buyer, features, specifications, benefits, objections, tone, primary keyword, and conversion goal. It should also tell AI not to invent facts.

Can AI help with WooCommerce product descriptions?

Yes. AI can create WooCommerce short descriptions, long descriptions, product FAQs, benefit bullets, SEO titles, meta descriptions, and image alt text for product pages.

How do I avoid generic AI product descriptions?

Add specific product facts, buyer needs, use cases, limitations, benefits, and brand tone. Ask AI to remove vague phrases and explain features in buyer-friendly language.

Should AI write product SEO keywords?

AI can suggest keyword ideas and related terms, but important product keywords should be validated with keyword research, Search Console data, or ecommerce SEO tools.

Can AI rewrite supplier product descriptions?

Yes. AI can help rewrite supplier descriptions into more unique, useful, and buyer-focused product copy. You must still keep technical facts accurate and avoid inventing features.

Conclusion

AI can make ecommerce product description writing faster, but the quality depends on the prompt and the product information you provide. A strong ecommerce prompt gives AI the product facts, target buyer, use cases, benefits, objections, SEO keyword, tone, and conversion goal. This helps create product copy that is clearer, more useful, and easier for customers to trust.

Use these 30 AI prompts as a practical product copy workflow. Start with product data, write short and long descriptions, turn features into benefits, create product FAQs, optimize metadata, write image alt text, plan internal links, and promote the product with social media copy. For stronger SEO planning, connect this guide with ChatGPT prompts for keyword research, AI prompts for meta titles and descriptions, and AI prompts for social media posts.

Image SEO

Featured image alt text: Futuristic ecommerce AI dashboard with product description editor, product cards, SEO keyword panel, conversion copy blocks, review insights, and neon prompt generator interface.

Featured image title: AI Prompts for Ecommerce Product Descriptions

SEO Metadata

SEO title: 30 AI Prompts for Ecommerce Product Descriptions

Meta description: Use 30 AI prompts for ecommerce product descriptions, SEO product copy, WooCommerce descriptions, product FAQs, sales text, and metadata.

Focus keyword: AI prompts for ecommerce product descriptions

Secondary keywords: ecommerce product description prompts, AI product description prompts, ChatGPT product description prompts, WooCommerce product description prompts, Shopify product description prompts, SEO product descriptions, ecommerce copywriting prompts, product page prompts, product FAQ prompts, product sales copy prompts

Social media content is easy to create, but difficult to make useful, consistent, and connected to a real business goal. A weak AI prompt usually creates generic captions, random hashtags, and posts that sound like every other brand. A strong social media prompt gives AI the platform, audience, offer, tone, message, content angle, call to action, and the exact format you need.

This guide gives you 30 professional AI prompts for social media posts. These prompts are built for real content workflows: Facebook posts, LinkedIn posts, X posts, Instagram captions, content repurposing, product announcements, blog promotion, service promotion, carousel ideas, short video scripts, engagement posts, launch posts, personal brand posts, and social media content calendars.

This post focuses only on social media content ideas and post creation. It does not replace SEO content writing, keyword research, or meta snippet optimization. For those tasks, use our related guides: ChatGPT prompts for SEO content writing, ChatGPT prompts for keyword research, AI prompts for meta titles and descriptions, and AI prompts for website copywriting.

View All 30 AI Social Media Prompts

What Are AI Prompts for Social Media Posts?

AI prompts for social media posts are structured instructions that help ChatGPT or another AI tool create better captions, hooks, post ideas, content calendars, carousel concepts, promotional posts, and platform-specific social media content. A good prompt explains the platform, target audience, post goal, tone, offer, format, and call to action.

A basic prompt might say, “write a Facebook post about my blog post.” A professional prompt says, “Act as a social media strategist, create a Facebook company page post for this blog article, target website owners, use a helpful tone, write a short hook, explain the value, add a clear CTA, and avoid hype.” The second prompt gives AI enough context to produce something usable.

These prompts are useful for small businesses, agencies, freelancers, content marketers, bloggers, ecommerce stores, WordPress website owners, SaaS teams, and service businesses that need consistent social media content without publishing generic posts that do not support the brand.

Why Social Media Prompts Matter

Social media prompts matter because each platform has a different style, audience expectation, and content rhythm. A LinkedIn post usually needs a different tone than an Instagram caption. A Facebook company page post should not sound the same as a personal brand story. A post promoting a blog article should not read like a sales page.

How to Use These Social Media Prompts

Each prompt below is copy-paste ready. Replace the bracketed parts such as [topic], [platform], [audience], [offer], [link], [tone], and [CTA]. The more specific your input is, the better the post will be.

Use these prompts after you know what the post should achieve. For example, a post can promote a blog article, explain a service, announce a product, start a conversation, build trust, repurpose an article, or bring people to a landing page. If you need the landing page copy first, use AI prompts for landing pages. If you need website text, use AI prompts for website copywriting.

What Should a Good Social Media Prompt Include?

Platform

Tell AI whether the post is for Facebook, LinkedIn, X, Instagram, or another channel.

Audience

Define who should care: business owners, developers, marketers, customers, followers, or local clients.

Post goal

Clarify whether the goal is clicks, comments, awareness, leads, product interest, or trust building.

CTA

Give AI a clear next step, such as reading a post, booking a call, requesting a quote, or commenting.

30 AI Prompts for Social Media Posts

Below are 30 professional AI prompts for social media posts. Each prompt is focused on one specific task, so you can create better posts for different platforms, content goals, and business use cases.

1. Facebook Company Page Post Prompt

This prompt creates a professional Facebook post for a business page. It is useful for promoting blog posts, services, products, announcements, and helpful resources.

Best forFacebook business posts
OutputPost text with CTA
Use whenYou need a company page post
Copy-paste prompt

How to customize it: Add the exact post URL and the real reader benefit. This prevents generic “check out our latest article” style posts.

2. LinkedIn Post Prompt

This prompt creates a LinkedIn post with a more professional angle. It is useful for B2B content, agency updates, educational posts, and service-related insights.

Best forLinkedIn posts
OutputProfessional post draft
Use whenYou want business visibility
Copy-paste prompt

Why it works: LinkedIn posts need a stronger point of view than normal promotional captions. This prompt asks for insight, not only promotion.

3. X Post Thread Prompt

This prompt turns a topic into a short X thread. It is useful when one idea needs several short posts instead of one long caption.

Best forX threads
OutputThread posts
Use whenYou need short structured ideas
Copy-paste prompt

How to use it: Use this for educational topics, SEO tips, AI workflows, product lessons, and practical business advice.

4. Instagram Caption Prompt

This prompt creates Instagram captions that feel more natural and less like generic AI text. It works for product posts, service updates, carousels, and educational content.

Best forInstagram captions
OutputCaption and hashtag ideas
Use whenYou need visual post text
Copy-paste prompt

Why it works: Instagram captions should support the visual. This prompt asks for the image context before writing.

5. Blog Post Promotion Prompt

This prompt promotes a blog post across social media without making the post sound like a boring link drop.

Best forBlog promotion
OutputPlatform-specific post drafts
Use whenYou publish a new article
Copy-paste prompt

How to customize it: Add the real article benefit. For example, “copy-paste prompts,” “working code examples,” or “SEO checklist.”

6. Service Promotion Post Prompt

This prompt creates social posts for promoting a service without sounding too aggressive or sales-heavy.

Best forService businesses
OutputService post variations
Use whenYou want leads or quote requests
Copy-paste prompt

Why it works: Service posts work better when they focus on the customer problem and the result, not only “we offer this service.”

Need social media posts that support your website goals?

If you cannot turn your blog posts, services, products, or landing pages into strong social media content, we can help create a content workflow that connects social posts with your website and SEO strategy.

Contact Us

7. Product Announcement Prompt

This prompt creates product announcement posts for new products, digital products, tools, plugins, templates, or ecommerce items.

Best forProduct launches
OutputAnnouncement post variations
Use whenYou launch or feature a product
Copy-paste prompt

How to customize it: Add the strongest product benefit, not only the feature list.

8. Ecommerce Product Post Prompt

This prompt creates social media posts for ecommerce product promotion without turning every caption into a hard sell.

Best forEcommerce posts
OutputProduct post drafts
Use whenYou promote a product page
Copy-paste prompt

Why it works: Ecommerce posts should connect the product with a real use case, not only list features.

9. Educational Social Post Prompt

This prompt creates educational posts that teach one useful idea. It is ideal for building authority without directly selling.

Best forEducational content
OutputHelpful post variations
Use whenYou want trust and authority
Copy-paste prompt

How to use it: Use this for SEO tips, WordPress advice, website design lessons, AI workflows, and business education posts.

10. Engagement Question Prompt

This prompt creates posts designed to start real comments and conversations without using empty engagement bait.

Best forComments and discussion
OutputQuestion-based posts
Use whenYou want audience interaction
Copy-paste prompt

Why it works: Good engagement posts ask questions that are useful for both the audience and the business.

11. Carousel Post Idea Prompt

This prompt turns one topic into a carousel structure for LinkedIn, Instagram, or Facebook.

Best forCarousel content
OutputSlide-by-slide outline
Use whenYou need visual social content
Copy-paste prompt

How to use it: Use this to turn blog sections, checklists, tutorials, and service explanations into visual posts.

12. Short Video Script Prompt

This prompt creates a short video script for Reels, Shorts, TikTok, LinkedIn video, or Facebook video.

Best forShort videos
OutputHook, script, shot ideas
Use whenYou want video content
Copy-paste prompt

Why it works: Short video needs fast structure. This prompt separates hook, script, visuals, and caption.

Want to turn one idea into many social media formats?

We can help repurpose your blog posts, website pages, service content, and product pages into platform-specific posts, carousels, short videos, and content calendars.

Build My Content Workflow

13. Content Repurposing Prompt

This prompt turns one blog article, page, newsletter, or video into multiple social media posts.

Best forRepurposing content
OutputMultiple post variations
Use whenYou already have content
Copy-paste prompt

How to customize it: Paste the original content or a summary. This is useful after publishing SEO articles.

14. Personal Brand Post Prompt

This prompt creates personal brand posts that share a lesson, opinion, process, or experience without sounding fake.

Best forFounders and experts
OutputPersonal post draft
Use whenYou want human authority
Copy-paste prompt

Why it works: Personal brand posts should be based on real experience. This prompt tells AI not to invent stories.

15. Case Study Social Post Prompt

This prompt creates social posts from a completed project, customer result, portfolio item, or case study.

Best forPortfolio and proof
OutputCase study post drafts
Use whenYou completed a project
Copy-paste prompt

How to use it: This is useful for web design, SEO, WordPress, ecommerce, app, and service project portfolios.

16. Testimonial Post Prompt

This prompt turns a customer review or testimonial into a social media post without making it sound exaggerated.

Best forSocial proof
OutputReview-based post
Use whenYou have customer feedback
Copy-paste prompt

Why it works: Testimonials are strongest when they stay believable and specific.

17. Event or Webinar Post Prompt

This prompt creates posts for promoting an event, webinar, workshop, live stream, or online session.

Best forEvents and webinars
OutputEvent promotion posts
Use whenYou need registrations
Copy-paste prompt

How to customize it: Add real agenda points and speaker details. Avoid letting AI invent event information.

18. Offer or Sale Post Prompt

This prompt creates promotional posts for real offers, discounts, seasonal campaigns, or limited-time deals.

Best forOffers and campaigns
OutputPromotion posts
Use whenYou have a real offer
Copy-paste prompt

Why it works: Promotional posts should be clear and honest. This prompt prevents fake urgency and overpromising.

Need social content that builds trust, not only noise?

We can help create social content from real projects, customer reviews, campaigns, offers, and website content so your posts support both trust and conversions.

Improve My Social Content

19. Content Calendar Prompt

This prompt creates a practical social media content calendar for a week, month, or campaign period.

Best forContent planning
OutputCalendar table
Use whenYou need consistency
Copy-paste prompt

How to use it: Use this to plan your social posts around new blog posts, products, services, and seasonal campaigns.

20. Brand Voice Social Prompt

This prompt rewrites social posts so they match your brand voice instead of sounding like generic AI text.

Best forBrand consistency
OutputRewritten social post
Use whenAI text sounds wrong
Copy-paste prompt

Why it works: Brand voice editing is one of the easiest ways to make AI social posts feel more human and consistent.

21. Hashtag Research Prompt

This prompt creates relevant hashtag ideas without stuffing every post with broad, useless tags.

Best forHashtag planning
OutputGrouped hashtag ideas
Use whenYou need relevant tags
Copy-paste prompt

How to use it: Hashtags should support discovery, not replace a clear post message.

22. Hook Generator Prompt

This prompt creates opening hooks for posts, captions, threads, carousels, and short videos.

Best forBetter openings
OutputHook variations
Use whenThe first line is weak
Copy-paste prompt

Why it works: A strong hook helps the post get read, but it must still match the real content.

23. CTA Generator Prompt

This prompt creates natural calls to action for different post goals without sounding forced.

Best forCall to action writing
OutputCTA variations
Use whenThe post needs a next step
Copy-paste prompt

How to use it: Match the CTA to the page destination. A blog post CTA should not sound like a hard sales pitch.

24. Social Media Post Audit Prompt

This prompt reviews a social media post before publishing and suggests improvements.

Best forPost editing
OutputAudit and improved version
Use whenYou want quality control
Copy-paste prompt

Why it works: AI can help edit social content, but the best result comes when you ask it to critique before rewriting.

Want better hooks, CTAs, and platform-specific posts?

We can help create repeatable social media templates, post structures, content calendars, and repurposing workflows for your website content and business offers.

Create My Social Workflow

25. Social Media A/B Test Prompt

This prompt creates multiple post variations for testing different hooks, angles, and CTAs.

Best forTesting post angles
OutputA/B test variants
Use whenYou want to compare versions
Copy-paste prompt

How to use it: Test different angles over time instead of posting the same message repeatedly.

26. Social Media Campaign Prompt

This prompt creates a campaign plan for launching a product, promoting a service, or driving traffic to a page.

Best forCampaign planning
OutputCampaign post sequence
Use whenYou need multiple posts
Copy-paste prompt

Why it works: Campaigns need a sequence, not random isolated posts.

27. AI Prompt for Social Media Post Ideas

This prompt generates social media content ideas for a business or niche without writing the posts yet.

Best forIdea generation
OutputPost idea table
Use whenYou are planning topics
Copy-paste prompt

How to use it: Use this before building a content calendar or asking AI to write full post drafts.

28. Multi-Platform Adaptation Prompt

This prompt adapts one post idea into several platform-specific versions.

Best forPlatform adaptation
OutputFacebook, LinkedIn, X, Instagram versions
Use whenYou do not want duplicate posts
Copy-paste prompt

Why it works: One idea can be used across platforms, but each platform needs its own style and format.

29. Social Media Reporting Prompt

This prompt helps analyze social media performance and turn the results into content improvement ideas.

Best forPerformance review
OutputInsights and next steps
Use whenYou have post data
Copy-paste prompt

How to use it: Use real post performance data to make the next content calendar better.

30. Complete Social Media Workflow Prompt

This prompt creates a complete repeatable workflow for planning, writing, adapting, publishing, and reviewing social media content.

Best forFull social workflow
OutputRepeatable process
Use whenYou want a system
Copy-paste prompt

Why it works: Social media works better as a system. This prompt connects ideas, posts, platforms, CTAs, publishing, and performance review.

Need a full AI-assisted social media content system?

If you want social media posts, content calendars, blog repurposing, platform-specific captions, product posts, or campaign workflows that support your business goals, we can help.

Contact Us

Social Media Prompt Best Practices

AI can create social media posts quickly, but the best results come from specific instructions and human editing. A good social prompt should define the platform, audience, post goal, tone, CTA, and the exact message you want to communicate. Without that context, AI often creates generic captions that sound polished but do not help your business.

Common AI Social Media Mistakes

The biggest mistake is asking AI to write social media posts without platform, audience, or goal. The result may look clean, but it often feels empty. Good social media content needs a clear reason to exist: teaching something, starting a conversation, promoting a useful offer, building trust, or guiding people to a relevant page.

AI Social Media Posts FAQ

Can AI write social media posts?

Yes, AI can write social media posts, captions, hooks, carousels, content calendars, and short video scripts. The quality depends on how specific the prompt is and how carefully the result is edited before publishing.

What is the best AI prompt for social media posts?

The best prompt includes the platform, audience, post goal, topic, brand tone, CTA, link, and any important details the post must mention or avoid.

Should I use the same AI post on every platform?

No. One idea can be adapted across platforms, but the wording, length, hook, CTA, and style should be changed for Facebook, LinkedIn, X, Instagram, or other channels.

Can AI create a social media content calendar?

Yes. AI can help create content calendars with post topics, platforms, goals, hooks, CTAs, and publishing order. You should still adjust the calendar based on your business priorities and available content assets.

How do I make AI social media posts less generic?

Add real context, examples, audience details, brand voice, platform rules, product benefits, customer problems, and clear quality instructions. Then edit the output manually before publishing.

Can AI repurpose blog posts into social media posts?

Yes. AI is very useful for turning blog posts into Facebook posts, LinkedIn posts, X threads, Instagram captions, carousel ideas, and short video scripts.

Conclusion

AI can help you create better social media posts when you use it with clear, platform-specific prompts. Instead of asking for random captions, give AI the platform, audience, goal, tone, message, CTA, and real context. This makes the output more useful, more specific, and easier to edit.

Use these 30 AI prompts as a practical social media workflow. Start with post ideas, adapt them to each platform, write stronger hooks, create CTAs, repurpose blog posts, plan content calendars, and review performance. For stronger website and SEO alignment, connect your social content with ChatGPT prompts for SEO content writing, AI prompts for website copywriting, and AI prompts for landing pages.

Image SEO

Featured image alt text: Futuristic AI social media content dashboard with Facebook, LinkedIn, X and Instagram post cards, caption editor, content calendar, engagement charts, and neon prompt generator interface.

Featured image title: AI Prompts for Social Media Posts

SEO Metadata

SEO title: 30 AI Prompts for Social Media Posts

Meta description: Use 30 AI prompts for social media posts, Facebook, LinkedIn, X, Instagram captions, content calendars, hooks, CTAs, and post ideas.

Focus keyword: AI prompts for social media posts

Secondary keywords: social media prompts, AI social media prompts, ChatGPT prompts for social media, Facebook post prompts, LinkedIn post prompts, Instagram caption prompts, X post prompts, social media content ideas, AI prompts for content calendars, social media post ideas

JavaScript drag and drop features are used in many practical websites and web apps. They can help users reorder tasks, move cards between Kanban columns, upload files, preview images, build forms, organize dashboards, assign team members, create sortable galleries, manage menus, and build more interactive user interfaces without relying on heavy frameworks.

In this guide, you will find 30 JavaScript drag and drop examples for real website projects, including sortable lists, Kanban boards, file upload zones, image previews, gallery reordering, shopping cart interactions, two-list selectors, project planners, form builders, page section builders, dashboard layouts, menu builders, drag-to-delete actions, quiz matching games, mobile-friendly touch drag, and accessible keyboard-supported drag and drop UI patterns.

This post focuses on practical drag and drop JavaScript logic, including the HTML Drag and Drop API, pointer-based dragging, sortable UI patterns, drop zones, drag handles, column movement, file validation, image previews, item reordering, visual drop feedback, state updates, mobile-friendly interactions, and copy-paste-ready HTML, CSS, and JavaScript examples. For related JavaScript UI features, you can also explore our JavaScript localStorage examples, JavaScript form validation examples, JavaScript Fetch API examples, and JavaScript calculator examples.

View All 30 JavaScript Drag and Drop Examples

What Is JavaScript Drag and Drop?

JavaScript drag and drop is a browser-based interaction pattern that lets users click, hold, move, and release items inside a web page. It can be used to reorder list items, move cards between columns, upload files into a drop zone, organize images, assign tasks, build page sections, arrange dashboard widgets, and create more natural user interfaces.

The most common approach is the built-in HTML Drag and Drop API, which uses events such as dragstart, dragover, drop, and dragend. For more custom interactions, JavaScript can also use pointer, mouse, or touch events to create drag behavior that works better on mobile screens and complex UI layouts.

Drag and drop is useful because it turns static pages into interactive tools. Instead of clicking many buttons or opening extra settings panels, users can move items directly where they want them. A task card can be moved to “Done”, an image can be reordered in a gallery, a file can be dropped into an upload box, and a menu item can be arranged visually.

Why Drag and Drop Features Matter

Drag and drop features matter because they make complex actions feel simple. Many web apps need users to organize, sort, assign, upload, compare, or arrange information. A good drag and drop interface can reduce clicks, make the workflow faster, and help users understand changes visually.

Drag and drop should still be designed carefully. Users need clear visual feedback, obvious drop zones, mobile-friendly controls, accessible alternatives, and predictable behavior. A sortable list should not feel jumpy, a file upload zone should validate files clearly, and a Kanban board should show exactly where a card will land before the user releases it.

JavaScript Drag and Drop Use Cases

There are many practical JavaScript drag and drop use cases for websites, ecommerce stores, admin dashboards, project management tools, booking systems, learning apps, portfolio pages, file managers, and content management interfaces.

A simple drag and drop feature may only reorder a small list. A more advanced feature may move cards between Kanban columns, validate dropped files, generate image previews, update counters, save layout order, assign users to projects, create a form builder, or support both pointer dragging and keyboard controls.

This guide focuses on JavaScript drag and drop examples, so every demo will include visible JavaScript, HTML, and CSS code. The examples are designed to be copy-paste friendly, practical for real websites, and different in layout, drag behavior, drop logic, interface style, UI feedback, responsive design, and real-world use case.

What Should a Good Drag and Drop Component Include?

A good drag and drop component should feel obvious, stable, responsive, and useful. Users should know what can be dragged, where it can be dropped, what changed after the drop action, and how to complete the same task if dragging is difficult on their device.

Clear draggable items

Cards, files, list rows, images, widgets, or menu items should look interactive before the user starts dragging.

Visible drop zones

Drop areas should be easy to recognize and should react visually when an item is dragged over them.

Reliable state updates

The interface should update order, counters, totals, selected items, previews, or status labels after every drop.

Responsive and accessible UI

Drag interactions should work well on desktop and include mobile or keyboard-friendly alternatives where needed.

Before building a JavaScript drag and drop feature, decide what the user is moving and why. Is the goal to sort tasks, upload files, reorder images, move cards between columns, assign people to projects, build a form, organize tags, compare products, or create a mobile-friendly touch interaction? A clear use case makes the code easier to write and the interface easier to understand.

You can combine JavaScript drag and drop features with many other website patterns. Sort order can be saved with JavaScript localStorage, dropped files can be validated with JavaScript form validation, product cards can connect with JavaScript calculator examples for totals and pricing, and advanced drag interfaces can later send updated data with JavaScript Fetch API requests.

30 JavaScript Drag and Drop Examples

Now let’s look at 30 JavaScript drag and drop examples for real website projects. Each example uses a different drag behavior, layout style, UI purpose, drop rule, feedback pattern, state update, validation method, mobile approach, or accessibility improvement, so you can build practical drag and drop interfaces with visible JavaScript, HTML, and CSS code.

1. Sortable Task List with Drag Handles

A sortable task list with drag handles is a practical JavaScript drag and drop example for dashboards, admin panels, task managers, priority lists, and workflow tools. Users can grab the handle, drag a task to a new position, and the priority numbers update automatically.

This example uses native HTML drag and drop, but the draggable element is the handle itself. That makes the interaction more reliable because users start dragging directly from the handle, while JavaScript moves the full task card in the list.

Example 01

Sortable Task List

Drag the handle on the left side of each task. Drop the task above or below another task to change the order.

⋮⋮ 1

Review landing page copy

Marketing task

High
⋮⋮ 2

Export product images

Design task

Medium
⋮⋮ 3

Fix checkout validation

Development task

High
⋮⋮ 4

Write support FAQ answers

Content task

Low
Current order 1 → 2 → 3 → 4

The full card moves when the handle is dragged. Numbers update after every drop.

JavaScript

(function () {
  function initSortableTaskList() {
    const root = document.querySelector("[data-vb-drag-sort]");
    if (!root) return;

    const list = root.querySelector("[data-vb-drag-sort-list]");
    const output = root.querySelector("[data-vb-drag-sort-output]");
    let draggedItem = null;

    function updateNumbers() {
      const items = Array.from(list.querySelectorAll("[data-vb-drag-sort-item]"));

      items.forEach(function (item, index) {
        const number = item.querySelector("[data-vb-drag-sort-number]");
        if (number) {
          number.textContent = index + 1;
        }
      });

      output.textContent = items.map(function (_, index) {
        return index + 1;
      }).join(" → ");
    }

    function clearOverStates() {
      list.querySelectorAll(".is-over").forEach(function (item) {
        item.classList.remove("is-over");
      });
    }

    list.addEventListener("dragstart", function (event) {
      const handle = event.target.closest("[data-vb-drag-sort-handle]");
      if (!handle) return;

      const item = handle.closest("[data-vb-drag-sort-item]");
      if (!item) return;

      draggedItem = item;
      item.classList.add("is-dragging");

      event.dataTransfer.effectAllowed = "move";
      event.dataTransfer.setData("text/plain", item.textContent.trim());
    });

    list.addEventListener("dragover", function (event) {
      event.preventDefault();

      if (!draggedItem) return;

      const targetItem = event.target.closest("[data-vb-drag-sort-item]");

      if (!targetItem || targetItem === draggedItem) return;

      const targetBox = targetItem.getBoundingClientRect();
      const shouldPlaceAfter = event.clientY > targetBox.top + targetBox.height / 2;

      clearOverStates();
      targetItem.classList.add("is-over");

      if (shouldPlaceAfter) {
        targetItem.insertAdjacentElement("afterend", draggedItem);
      } else {
        targetItem.insertAdjacentElement("beforebegin", draggedItem);
      }

      updateNumbers();
    });

    list.addEventListener("drop", function (event) {
      event.preventDefault();
      clearOverStates();

      if (draggedItem) {
        draggedItem.classList.remove("is-dragging");
      }

      draggedItem = null;
      updateNumbers();
    });

    list.addEventListener("dragend", function () {
      clearOverStates();

      if (draggedItem) {
        draggedItem.classList.remove("is-dragging");
      }

      draggedItem = null;
      updateNumbers();
    });

    updateNumbers();
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initSortableTaskList);
  } else {
    initSortableTaskList();
  }
})();

HTML

<div class="vb-drag-sort-demo">
  <div class="vb-drag-sort-wrap" data-vb-drag-sort>
    <div class="vb-drag-sort-intro">
      <span>Example 01</span>
      <h3>Sortable Task List</h3>
      <p>Drag the handle on the left side of each task. Drop the task above or below another task to change the order.</p>
    </div>

    <div class="vb-drag-sort-grid">
      <div class="vb-drag-sort-list" data-vb-drag-sort-list>
        <div class="vb-drag-sort-item" data-vb-drag-sort-item>
          <span class="vb-drag-sort-handle" draggable="true" data-vb-drag-sort-handle>⋮⋮</span>
          <strong class="vb-drag-sort-number" data-vb-drag-sort-number>1</strong>
          <div class="vb-drag-sort-text">
            <h4>Review landing page copy</h4>
            <p>Marketing task</p>
          </div>
          <em>High</em>
        </div>

        <div class="vb-drag-sort-item" data-vb-drag-sort-item>
          <span class="vb-drag-sort-handle" draggable="true" data-vb-drag-sort-handle>⋮⋮</span>
          <strong class="vb-drag-sort-number" data-vb-drag-sort-number>2</strong>
          <div class="vb-drag-sort-text">
            <h4>Export product images</h4>
            <p>Design task</p>
          </div>
          <em>Medium</em>
        </div>

        <div class="vb-drag-sort-item" data-vb-drag-sort-item>
          <span class="vb-drag-sort-handle" draggable="true" data-vb-drag-sort-handle>⋮⋮</span>
          <strong class="vb-drag-sort-number" data-vb-drag-sort-number>3</strong>
          <div class="vb-drag-sort-text">
            <h4>Fix checkout validation</h4>
            <p>Development task</p>
          </div>
          <em>High</em>
        </div>

        <div class="vb-drag-sort-item" data-vb-drag-sort-item>
          <span class="vb-drag-sort-handle" draggable="true" data-vb-drag-sort-handle>⋮⋮</span>
          <strong class="vb-drag-sort-number" data-vb-drag-sort-number>4</strong>
          <div class="vb-drag-sort-text">
            <h4>Write support FAQ answers</h4>
            <p>Content task</p>
          </div>
          <em>Low</em>
        </div>
      </div>

      <div class="vb-drag-sort-status">
        <span>Current order</span>
        <strong data-vb-drag-sort-output>1 → 2 → 3 → 4</strong>
        <p>The full card moves when the handle is dragged. Numbers update after every drop.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-drag-sort-demo,
.vb-drag-sort-demo * {
  box-sizing: border-box;
}

.vb-drag-sort-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 36px;
  background:
    radial-gradient(circle at 14% 18%, rgba(37, 99, 235, 0.18), transparent 34%),
    radial-gradient(circle at 90% 12%, rgba(249, 115, 22, 0.18), transparent 35%),
    linear-gradient(135deg, #eff6ff 0%, #fff7ed 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-drag-sort-wrap {
  max-width: 1120px;
  margin: 0 auto;
}

.vb-drag-sort-intro {
  max-width: 760px;
  margin-bottom: 22px;
}

.vb-drag-sort-intro span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-drag-sort-intro h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-drag-sort-intro p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-drag-sort-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(260px, 0.8fr);
  gap: 18px;
}

.vb-drag-sort-list {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.25);
  box-shadow: 0 20px 58px rgba(15, 23, 42, 0.10);
}

.vb-drag-sort-item {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 16px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.28);
  box-shadow: 0 12px 30px rgba(15, 23, 42, 0.07);
  transition: opacity 0.2s ease, transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.vb-drag-sort-item.is-dragging {
  opacity: 0.42;
  transform: scale(0.98);
  border-color: rgba(37, 99, 235, 0.70);
  background: #eff6ff;
}

.vb-drag-sort-item.is-over {
  border-color: rgba(249, 115, 22, 0.85);
  background: #fff7ed;
}

.vb-drag-sort-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 46px;
  border-radius: 15px;
  background: #eef2ff;
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  user-select: none;
}

.vb-drag-sort-handle:active {
  cursor: grabbing;
}

.vb-drag-sort-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-drag-sort-text h4 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px !important;
  line-height: 1.25 !important;
  font-weight: 900 !important;
}

.vb-drag-sort-text p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.35;
  font-weight: 700;
}

.vb-drag-sort-item em {
  min-width: 72px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #fff7ed;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 12px;
  font-style: normal;
  font-weight: 900;
  text-align: center;
}

.vb-drag-sort-status {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 26px;
  border-radius: 30px;
  background:
    radial-gradient(circle at 10% 15%, rgba(34, 211, 238, 0.22), transparent 34%),
    linear-gradient(135deg, #0f172a, #312e81) !important;
  box-shadow: 0 22px 70px rgba(15, 23, 42, 0.22);
}

.vb-drag-sort-status span {
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-drag-sort-status strong {
  margin: 12px 0;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 4vw, 48px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-drag-sort-status p {
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

@media (max-width: 820px) {
  .vb-drag-sort-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-drag-sort-item {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .vb-drag-sort-number,
  .vb-drag-sort-item em {
    display: none;
  }
}

This sortable JavaScript drag and drop task list is useful for dashboards, task planners, admin tools, editorial workflows, and project management interfaces.

2. Kanban Board with Multiple Columns

A JavaScript Kanban board is a strong drag and drop example for project management dashboards, CRM pipelines, content planning tools, support ticket boards, and workflow interfaces. Users can move cards between columns to update their status.

This example uses native drag and drop for workflow columns. It includes multiple columns, live counters, active drop highlighting, empty states, and automatic status updates after a card is dropped.

Example 02

Kanban Board

Drag cards between workflow columns. Each column updates its counter and empty state automatically.

To Do 0
Create landing page wireframe Design
Collect customer testimonials Content
In Progress 0
Build checkout layout Development
Review 0
Check mobile navigation QA
Done 0
Publish pricing update Completed

JavaScript

(function () {
  const root = document.querySelector("[data-vb-dd-two]");
  if (!root) return;

  const zones = root.querySelectorAll("[data-vb-dd-two-zone]");
  const columns = root.querySelectorAll("[data-vb-dd-two-column]");
  let activeCard = null;

  function updateCounts() {
    columns.forEach(function (column) {
      const counter = column.querySelector("[data-vb-dd-two-count]");
      const count = column.querySelectorAll("[data-vb-dd-two-card]").length;
      counter.textContent = count;
    });
  }

  root.addEventListener("dragstart", function (event) {
    const card = event.target.closest("[data-vb-dd-two-card]");
    if (!card) return;

    activeCard = card;
    card.classList.add("is-dragging");
    event.dataTransfer.effectAllowed = "move";
    event.dataTransfer.setData("text/plain", card.textContent.trim());
  });

  root.addEventListener("dragend", function (event) {
    const card = event.target.closest("[data-vb-dd-two-card]");
    if (!card) return;

    card.classList.remove("is-dragging");
    activeCard = null;

    zones.forEach(function (zone) {
      zone.classList.remove("is-over");
    });

    updateCounts();
  });

  zones.forEach(function (zone) {
    zone.addEventListener("dragover", function (event) {
      event.preventDefault();
      zone.classList.add("is-over");
    });

    zone.addEventListener("dragleave", function () {
      zone.classList.remove("is-over");
    });

    zone.addEventListener("drop", function (event) {
      event.preventDefault();

      if (!activeCard) return;

      zone.appendChild(activeCard);
      zone.classList.remove("is-over");
      updateCounts();
    });
  });

  updateCounts();
})();

HTML

<div class="vb-dd-two-demo">
  <div class="vb-dd-two-wrap" data-vb-dd-two>
    <div class="vb-dd-two-intro">
      <span>Example 02</span>
      <h3>Kanban Board</h3>
      <p>Drag cards between workflow columns. Each column updates its counter and empty state automatically.</p>
    </div>

    <div class="vb-dd-two-board">
      <section class="vb-dd-two-column" data-vb-dd-two-column>
        <header>
          <span>To Do</span>
          <strong data-vb-dd-two-count>0</strong>
        </header>
        <div class="vb-dd-two-zone" data-vb-dd-two-zone>
          <article class="vb-dd-two-card" draggable="true" data-vb-dd-two-card>
            <strong>Create landing page wireframe</strong>
            <small>Design</small>
          </article>
          <article class="vb-dd-two-card" draggable="true" data-vb-dd-two-card>
            <strong>Collect customer testimonials</strong>
            <small>Content</small>
          </article>
        </div>
      </section>

      <section class="vb-dd-two-column" data-vb-dd-two-column>
        <header>
          <span>In Progress</span>
          <strong data-vb-dd-two-count>0</strong>
        </header>
        <div class="vb-dd-two-zone" data-vb-dd-two-zone>
          <article class="vb-dd-two-card" draggable="true" data-vb-dd-two-card>
            <strong>Build checkout layout</strong>
            <small>Development</small>
          </article>
        </div>
      </section>

      <section class="vb-dd-two-column" data-vb-dd-two-column>
        <header>
          <span>Review</span>
          <strong data-vb-dd-two-count>0</strong>
        </header>
        <div class="vb-dd-two-zone" data-vb-dd-two-zone>
          <article class="vb-dd-two-card" draggable="true" data-vb-dd-two-card>
            <strong>Check mobile navigation</strong>
            <small>QA</small>
          </article>
        </div>
      </section>

      <section class="vb-dd-two-column" data-vb-dd-two-column>
        <header>
          <span>Done</span>
          <strong data-vb-dd-two-count>0</strong>
        </header>
        <div class="vb-dd-two-zone" data-vb-dd-two-zone>
          <article class="vb-dd-two-card" draggable="true" data-vb-dd-two-card>
            <strong>Publish pricing update</strong>
            <small>Completed</small>
          </article>
        </div>
      </section>
    </div>
  </div>
</div>

CSS

.vb-dd-two-demo,
.vb-dd-two-demo * {
  box-sizing: border-box;
}

.vb-dd-two-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 38px;
  background: linear-gradient(135deg, #ecfdf5 0%, #eff6ff 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-dd-two-wrap {
  max-width: 1180px;
  margin: 0 auto;
}

.vb-dd-two-intro {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 22px;
}

.vb-dd-two-intro span {
  display: inline-flex;
  margin-bottom: 12px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-dd-two-intro h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-dd-two-intro p {
  max-width: 430px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-dd-two-board {
  display: grid;
  grid-template-columns: repeat(4, minmax(230px, 1fr));
  gap: 16px;
  overflow-x: auto;
  padding-bottom: 8px;
}

.vb-dd-two-column {
  min-width: 230px;
  display: flex;
  flex-direction: column;
  min-height: 390px;
  padding: 14px;
  border-radius: 28px;
  background: rgba(255, 255, 255, 0.78);
  border: 1px solid rgba(148, 163, 184, 0.25);
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.09);
}

.vb-dd-two-column header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
  padding: 12px 12px 0;
}

.vb-dd-two-column header span {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  line-height: 1.2;
  font-weight: 950;
}

.vb-dd-two-column header strong {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-dd-two-zone {
  display: grid;
  align-content: start;
  gap: 12px;
  flex: 1;
  min-height: 300px;
  padding: 10px;
  border-radius: 22px;
  border: 1px dashed rgba(148, 163, 184, 0.45);
  background: linear-gradient(135deg, rgba(248, 250, 252, 0.88), rgba(239, 246, 255, 0.70));
  transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
}

.vb-dd-two-zone:empty::after {
  content: "Drop card here";
  display: grid;
  place-items: center;
  min-height: 72px;
  border-radius: 18px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  font-weight: 850;
  border: 1px dashed rgba(148, 163, 184, 0.45);
}

.vb-dd-two-zone.is-over {
  border-color: rgba(37, 99, 235, 0.75);
  background: #eff6ff;
  box-shadow: inset 0 0 0 3px rgba(37, 99, 235, 0.10);
}

.vb-dd-two-card {
  padding: 16px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.08);
  cursor: grab;
  transition: transform 0.2s ease, opacity 0.2s ease, box-shadow 0.2s ease;
}

.vb-dd-two-card:active {
  cursor: grabbing;
}

.vb-dd-two-card.is-dragging {
  opacity: 0.42;
  transform: rotate(1deg) scale(0.98);
  box-shadow: none;
}

.vb-dd-two-card strong {
  display: block;
  margin-bottom: 9px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  line-height: 1.35;
  font-weight: 900;
}

.vb-dd-two-card small {
  display: inline-flex;
  padding: 7px 10px;
  border-radius: 999px;
  background: #f1f5f9;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 12px;
  font-weight: 850;
}

@media (max-width: 860px) {
  .vb-dd-two-intro {
    display: grid;
  }

  .vb-dd-two-intro p {
    max-width: 720px;
  }
}

This JavaScript Kanban board is useful for project management dashboards, CRM pipelines, editorial planning boards, support ticket systems, and team workflow interfaces.

3. Drag and Drop File Upload Zone

A drag and drop file upload zone improves forms, dashboards, support portals, job application pages, document upload pages, and file manager interfaces. Users can drop files into a clear upload area instead of only using the default file input.

This demo validates files in the browser and displays selected file names, file types, file sizes, remove buttons, and a live selected file counter. It does not upload files to a server, so it is safe to use as a front-end UI example.

Example 03

File Upload Zone

Drop PDF, PNG, JPG, JPEG, or TXT files into the upload area. The file list updates instantly.

Selected files 0
No files selected yet.

JavaScript

(function () {
  const root = document.querySelector("[data-vb-dd-three]");
  if (!root) return;

  const zone = root.querySelector("[data-vb-dd-three-zone]");
  const input = root.querySelector("[data-vb-dd-three-input]");
  const list = root.querySelector("[data-vb-dd-three-list]");
  const message = root.querySelector("[data-vb-dd-three-message]");
  const count = root.querySelector("[data-vb-dd-three-count]");

  const allowedTypes = ["pdf", "png", "jpg", "jpeg", "txt"];
  let selectedFiles = [];

  function getExtension(fileName) {
    const parts = fileName.split(".");
    return parts.length > 1 ? parts.pop().toLowerCase() : "";
  }

  function formatSize(bytes) {
    if (bytes < 1024) return bytes + " B";
    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
    return (bytes / (1024 * 1024)).toFixed(1) + " MB";
  }

  function setMessage(text, type) {
    message.textContent = text;
    message.classList.remove("is-error", "is-success");

    if (type) {
      message.classList.add(type);
    }
  }

  function createFileRow(file, index) {
    const extension = getExtension(file.name);

    const row = document.createElement("div");
    row.className = "vb-dd-three-file";

    const type = document.createElement("span");
    type.className = "vb-dd-three-file-type";
    type.textContent = extension || "file";

    const text = document.createElement("div");

    const name = document.createElement("strong");
    name.className = "vb-dd-three-file-name";
    name.textContent = file.name;

    const size = document.createElement("span");
    size.className = "vb-dd-three-file-size";
    size.textContent = formatSize(file.size);

    const remove = document.createElement("button");
    remove.className = "vb-dd-three-remove";
    remove.type = "button";
    remove.setAttribute("aria-label", "Remove file");
    remove.setAttribute("data-vb-dd-three-remove", index);
    remove.textContent = "×";

    text.appendChild(name);
    text.appendChild(size);

    row.appendChild(type);
    row.appendChild(text);
    row.appendChild(remove);

    return row;
  }

  function renderFiles() {
    list.innerHTML = "";

    selectedFiles.forEach(function (file, index) {
      list.appendChild(createFileRow(file, index));
    });

    count.textContent = selectedFiles.length;

    if (selectedFiles.length === 0) {
      setMessage("No files selected yet.", "");
    }
  }

  function addFiles(fileList) {
    const incoming = Array.from(fileList);
    let added = 0;
    let rejected = 0;

    incoming.forEach(function (file) {
      const extension = getExtension(file.name);

      if (!allowedTypes.includes(extension)) {
        rejected += 1;
        return;
      }

      selectedFiles.push(file);
      added += 1;
    });

    renderFiles();

    if (added > 0 && rejected === 0) {
      setMessage(added + " file(s) added successfully.", "is-success");
    } else if (added > 0 && rejected > 0) {
      setMessage(added + " file(s) added. " + rejected + " file(s) rejected.", "is-error");
    } else {
      setMessage("File type not allowed. Use PDF, PNG, JPG, JPEG, or TXT.", "is-error");
    }
  }

  zone.addEventListener("dragover", function (event) {
    event.preventDefault();
    zone.classList.add("is-over");
  });

  zone.addEventListener("dragleave", function () {
    zone.classList.remove("is-over");
  });

  zone.addEventListener("drop", function (event) {
    event.preventDefault();
    zone.classList.remove("is-over");

    if (event.dataTransfer.files.length) {
      addFiles(event.dataTransfer.files);
    }
  });

  input.addEventListener("change", function () {
    if (input.files.length) {
      addFiles(input.files);
      input.value = "";
    }
  });

  list.addEventListener("click", function (event) {
    const removeButton = event.target.closest("[data-vb-dd-three-remove]");
    if (!removeButton) return;

    const index = Number(removeButton.getAttribute("data-vb-dd-three-remove"));
    selectedFiles.splice(index, 1);
    renderFiles();

    if (selectedFiles.length > 0) {
      setMessage("File removed. " + selectedFiles.length + " file(s) selected.", "is-success");
    }
  });

  renderFiles();
})();

HTML

<div class="vb-dd-three-demo">
  <div class="vb-dd-three-wrap" data-vb-dd-three>
    <div class="vb-dd-three-copy">
      <span>Example 03</span>
      <h3>File Upload Zone</h3>
      <p>Drop PDF, PNG, JPG, JPEG, or TXT files into the upload area. The file list updates instantly.</p>

      <div class="vb-dd-three-summary">
        <span>Selected files</span>
        <strong data-vb-dd-three-count>0</strong>
      </div>
    </div>

    <div class="vb-dd-three-uploader">
      <label class="vb-dd-three-zone" data-vb-dd-three-zone>
        <input type="file" multiple data-vb-dd-three-input accept=".pdf,.png,.jpg,.jpeg,.txt">
        <span class="vb-dd-three-icon">↥</span>
        <strong>Drop files here</strong>
        <small>or click to browse PDF, PNG, JPG, JPEG, and TXT files</small>
      </label>

      <div class="vb-dd-three-message" data-vb-dd-three-message>No files selected yet.</div>
      <div class="vb-dd-three-list" data-vb-dd-three-list></div>
    </div>
  </div>
</div>

CSS

.vb-dd-three-demo,
.vb-dd-three-demo * {
  box-sizing: border-box;
}

.vb-dd-three-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 38px;
  background: linear-gradient(135deg, #fdf2f8 0%, #eff6ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-dd-three-wrap {
  max-width: 1120px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(260px, 0.82fr) minmax(0, 1.18fr);
  gap: 20px;
  align-items: stretch;
}

.vb-dd-three-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 32px;
  background: linear-gradient(135deg, #0f172a, #1e1b4b) !important;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.24);
}

.vb-dd-three-copy > span {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-dd-three-copy h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-dd-three-copy p {
  margin: 0 0 22px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-dd-three-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
}

.vb-dd-three-summary span {
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 13px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-dd-three-summary strong {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  height: 44px;
  border-radius: 999px;
  background: #ffffff;
  color: #1e1b4b !important;
  -webkit-text-fill-color: #1e1b4b !important;
  font-size: 20px;
  font-weight: 950;
}

.vb-dd-three-uploader {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 32px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-dd-three-zone {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 250px;
  padding: 26px;
  border-radius: 28px;
  border: 2px dashed rgba(14, 165, 233, 0.45);
  background: linear-gradient(135deg, #f8fafc, #eff6ff);
  text-align: center;
  cursor: pointer;
  transition: transform 0.22s ease, border-color 0.22s ease, background 0.22s ease, box-shadow 0.22s ease;
}

.vb-dd-three-zone input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.vb-dd-three-zone.is-over {
  transform: translateY(-2px);
  border-color: rgba(236, 72, 153, 0.70);
  background: linear-gradient(135deg, #fdf2f8, #eff6ff);
  box-shadow: inset 0 0 0 5px rgba(236, 72, 153, 0.08);
}

.vb-dd-three-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 76px;
  height: 76px;
  margin-bottom: 16px;
  border-radius: 24px;
  background: linear-gradient(135deg, #0ea5e9, #7c3aed);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 42px;
  font-weight: 950;
  box-shadow: 0 18px 38px rgba(14, 165, 233, 0.25);
}

.vb-dd-three-zone strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(24px, 4vw, 38px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-dd-three-zone small {
  display: block;
  max-width: 430px;
  margin-top: 10px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 700;
}

.vb-dd-three-message {
  padding: 13px 15px;
  border-radius: 16px;
  background: #f8fafc;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-dd-three-message.is-error {
  background: #fef2f2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-dd-three-message.is-success {
  background: #ecfdf5;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
}

.vb-dd-three-list {
  display: grid;
  gap: 10px;
  max-height: 270px;
  overflow: auto;
}

.vb-dd-three-file {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 13px;
  border-radius: 18px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-dd-three-file-type {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 14px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 13px;
  font-weight: 950;
  text-transform: uppercase;
}

.vb-dd-three-file-name {
  display: block;
  margin-bottom: 3px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px;
  line-height: 1.3;
  font-weight: 900;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vb-dd-three-file-size {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 750;
}

.vb-dd-three-remove {
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 999px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 18px;
  font-weight: 950;
  cursor: pointer;
}

@media (max-width: 860px) {
  .vb-dd-three-wrap {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 540px) {
  .vb-dd-three-file {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .vb-dd-three-remove {
    grid-column: 1 / -1;
    width: 100%;
  }
}

This JavaScript drag and drop file upload zone is useful for contact forms, client dashboards, support tickets, job applications, document portals, and admin upload interfaces.

4. Image Upload Preview with Drag and Drop

An image upload preview with drag and drop is useful for profile forms, product upload tools, portfolio dashboards, review forms, support tickets, and admin panels. Users can drop image files into the upload area and immediately see visual previews before submitting anything.

Example 04

Image Upload Preview

Drop image files into the upload zone or use the browse button. JavaScript creates instant local previews.

0 Images selected
+
Drop images here PNG, JPG, JPEG, WEBP, and GIF files are supported.
No images selected yet.

JavaScript

(function () {
  const root = document.querySelector("[data-vb-imgup]");
  if (!root) return;

  const zone = root.querySelector("[data-vb-imgup-zone]");
  const input = root.querySelector("[data-vb-imgup-input]");
  const browse = root.querySelector("[data-vb-imgup-browse]");
  const grid = root.querySelector("[data-vb-imgup-grid]");
  const message = root.querySelector("[data-vb-imgup-message]");
  const count = root.querySelector("[data-vb-imgup-count]");

  let images = [];

  function formatSize(bytes) {
    if (bytes < 1024) return bytes + " B";
    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
    return (bytes / (1024 * 1024)).toFixed(1) + " MB";
  }

  function setMessage(text, type) {
    message.textContent = text;
    message.classList.remove("is-success", "is-error");
    if (type) {
      message.classList.add(type);
    }
  }

  function renderImages() {
    grid.innerHTML = "";

    images.forEach(function (image, index) {
      const card = document.createElement("div");
      card.className = "vb-imgup-card";

      const img = document.createElement("img");
      img.src = image.url;
      img.alt = image.file.name;

      const remove = document.createElement("button");
      remove.className = "vb-imgup-remove";
      remove.type = "button";
      remove.textContent = "×";
      remove.setAttribute("aria-label", "Remove image");
      remove.setAttribute("data-vb-imgup-remove", index);

      const body = document.createElement("div");
      body.className = "vb-imgup-card-body";

      const name = document.createElement("strong");
      name.textContent = image.file.name;

      const size = document.createElement("span");
      size.textContent = formatSize(image.file.size);

      body.appendChild(name);
      body.appendChild(size);
      card.appendChild(img);
      card.appendChild(remove);
      card.appendChild(body);
      grid.appendChild(card);
    });

    count.textContent = images.length;

    if (images.length === 0) {
      setMessage("No images selected yet.", "");
    }
  }

  function addImages(fileList) {
    const files = Array.from(fileList);
    let added = 0;
    let rejected = 0;

    files.forEach(function (file) {
      if (!file.type || !file.type.startsWith("image/")) {
        rejected += 1;
        return;
      }

      images.push({
        file: file,
        url: URL.createObjectURL(file)
      });

      added += 1;
    });

    renderImages();

    if (added > 0 && rejected === 0) {
      setMessage(added + " image(s) added successfully.", "is-success");
    } else if (added > 0 && rejected > 0) {
      setMessage(added + " image(s) added. " + rejected + " file(s) rejected.", "is-error");
    } else {
      setMessage("Only image files are allowed.", "is-error");
    }
  }

  browse.addEventListener("click", function () {
    input.click();
  });

  input.addEventListener("change", function () {
    if (input.files.length) {
      addImages(input.files);
      input.value = "";
    }
  });

  zone.addEventListener("dragover", function (event) {
    event.preventDefault();
    zone.classList.add("is-over");
  });

  zone.addEventListener("dragleave", function () {
    zone.classList.remove("is-over");
  });

  zone.addEventListener("drop", function (event) {
    event.preventDefault();
    zone.classList.remove("is-over");

    if (event.dataTransfer.files.length) {
      addImages(event.dataTransfer.files);
    }
  });

  grid.addEventListener("click", function (event) {
    const button = event.target.closest("[data-vb-imgup-remove]");
    if (!button) return;

    const index = Number(button.getAttribute("data-vb-imgup-remove"));

    if (images[index]) {
      URL.revokeObjectURL(images[index].url);
      images.splice(index, 1);
      renderImages();

      if (images.length > 0) {
        setMessage("Image removed. " + images.length + " image(s) selected.", "is-success");
      }
    }
  });

  renderImages();
})();

HTML

<div class="vb-imgup-demo">
  <div class="vb-imgup-wrap" data-vb-imgup>
    <div class="vb-imgup-copy">
      <span>Example 04</span>
      <h3>Image Upload Preview</h3>
      <p>Drop image files into the upload zone or use the browse button. JavaScript creates instant local previews.</p>

      <div class="vb-imgup-counter">
        <strong data-vb-imgup-count>0</strong>
        <span>Images selected</span>
      </div>
    </div>

    <div class="vb-imgup-panel">
      <div class="vb-imgup-zone" data-vb-imgup-zone>
        <div class="vb-imgup-icon">+</div>
        <strong>Drop images here</strong>
        <small>PNG, JPG, JPEG, WEBP, and GIF files are supported.</small>
        <button type="button" data-vb-imgup-browse>Browse Images</button>
        <input type="file" accept="image/png,image/jpeg,image/webp,image/gif" multiple data-vb-imgup-input>
      </div>

      <div class="vb-imgup-message" data-vb-imgup-message>No images selected yet.</div>
      <div class="vb-imgup-grid" data-vb-imgup-grid></div>
    </div>
  </div>
</div>

CSS

.vb-imgup-demo,
.vb-imgup-demo * {
  box-sizing: border-box;
}

.vb-imgup-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 12% 15%, rgba(236, 72, 153, 0.16), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(14, 165, 233, 0.18), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #eff6ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-imgup-wrap {
  max-width: 1120px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(260px, 0.82fr) minmax(0, 1.18fr);
  gap: 20px;
  align-items: stretch;
}

.vb-imgup-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(22px, 4vw, 36px);
  border-radius: 32px;
  background:
    radial-gradient(circle at 18% 14%, rgba(244, 114, 182, 0.22), transparent 38%),
    linear-gradient(135deg, #111827, #581c87) !important;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.25);
}

.vb-imgup-copy > span {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-imgup-copy h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-imgup-copy p {
  margin: 0 0 24px !important;
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-imgup-counter {
  display: grid;
  gap: 4px;
  padding: 18px;
  border-radius: 24px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
}

.vb-imgup-counter strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 48px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.06em;
}

.vb-imgup-counter span {
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 13px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-imgup-panel {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 32px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-imgup-zone {
  display: grid;
  place-items: center;
  min-height: 260px;
  padding: 26px;
  border-radius: 28px;
  border: 2px dashed rgba(236, 72, 153, 0.44);
  background:
    radial-gradient(circle at top, rgba(236, 72, 153, 0.13), transparent 42%),
    linear-gradient(135deg, #fdf2f8, #eff6ff);
  text-align: center;
  transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease, background 0.22s ease;
}

.vb-imgup-zone.is-over {
  transform: translateY(-2px);
  border-color: rgba(14, 165, 233, 0.78);
  background:
    radial-gradient(circle at top, rgba(14, 165, 233, 0.15), transparent 42%),
    linear-gradient(135deg, #eff6ff, #fdf2f8);
  box-shadow: inset 0 0 0 5px rgba(14, 165, 233, 0.10);
}

.vb-imgup-zone input {
  display: none;
}

.vb-imgup-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 78px;
  height: 78px;
  margin-bottom: 16px;
  border-radius: 26px;
  background: linear-gradient(135deg, #ec4899, #7c3aed);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 46px;
  line-height: 1;
  font-weight: 750;
  box-shadow: 0 18px 38px rgba(236, 72, 153, 0.24);
}

.vb-imgup-zone strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(24px, 4vw, 38px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-imgup-zone small {
  display: block;
  max-width: 430px;
  margin-top: 10px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 700;
}

.vb-imgup-zone button {
  margin-top: 18px;
  min-height: 44px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #ec4899, #7c3aed);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(124, 58, 237, 0.22);
}

.vb-imgup-message {
  padding: 13px 15px;
  border-radius: 16px;
  background: #f8fafc;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-imgup-message.is-success {
  background: #ecfdf5;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
}

.vb-imgup-message.is-error {
  background: #fef2f2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-imgup-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-imgup-card {
  position: relative;
  min-width: 0;
  overflow: hidden;
  border-radius: 20px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.08);
}

.vb-imgup-card img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 0.75;
  object-fit: cover;
  background: #e2e8f0;
}

.vb-imgup-card-body {
  display: grid;
  gap: 4px;
  padding: 12px;
}

.vb-imgup-card-body strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 13px;
  line-height: 1.25;
  font-weight: 900;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vb-imgup-card-body span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 750;
}

.vb-imgup-remove {
  position: absolute;
  top: 9px;
  right: 9px;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.78);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
  cursor: pointer;
}

@media (max-width: 860px) {
  .vb-imgup-wrap {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-imgup-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 460px) {
  .vb-imgup-grid {
    grid-template-columns: 1fr;
  }
}

This JavaScript image upload preview is useful for profile forms, product image upload dashboards, portfolio tools, support forms, review forms, and admin interfaces where users should see images before submitting them.

5. Reorder Image Gallery with Drag and Drop

A reorderable image gallery is useful for portfolio pages, ecommerce product galleries, media libraries, admin panels, travel websites, real estate listings, and any interface where image order matters. Users can drag image cards to change their display order visually.

Example 05

Reorder Image Gallery

Drag the small handle on any gallery card to reorder the grid. The numbers update automatically.

Gallery order 1 → 2 → 3 → 4 → 5 → 6
⋮⋮
1

Hero Product Image

Main ecommerce image

⋮⋮
2

Detail Closeup

Texture and material

⋮⋮
3

Lifestyle Scene

Real usage example

⋮⋮
4

Package Preview

Box and contents

⋮⋮
5

Mobile Crop

Social preview image

⋮⋮
6

Gallery Thumbnail

Small preview card

JavaScript

(function () {
  function initGallerySort() {
    const root = document.querySelector("[data-vb-galsort]");
    if (!root) return;

    const grid = root.querySelector("[data-vb-galsort-grid]");
    const output = root.querySelector("[data-vb-galsort-output]");
    let draggedCard = null;

    function updateOrder() {
      const cards = Array.from(grid.querySelectorAll("[data-vb-galsort-card]"));

      cards.forEach(function (card, index) {
        const number = card.querySelector("[data-vb-galsort-number]");
        if (number) {
          number.textContent = index + 1;
        }
      });

      output.textContent = cards.map(function (_, index) {
        return index + 1;
      }).join(" → ");
    }

    function clearOverStates() {
      grid.querySelectorAll(".is-over").forEach(function (card) {
        card.classList.remove("is-over");
      });
    }

    grid.addEventListener("dragstart", function (event) {
      const handle = event.target.closest("[data-vb-galsort-handle]");
      if (!handle) return;

      const card = handle.closest("[data-vb-galsort-card]");
      if (!card) return;

      draggedCard = card;
      card.classList.add("is-dragging");

      event.dataTransfer.effectAllowed = "move";
      event.dataTransfer.setData("text/plain", card.textContent.trim());
    });

    grid.addEventListener("dragover", function (event) {
      event.preventDefault();

      if (!draggedCard) return;

      const targetCard = event.target.closest("[data-vb-galsort-card]");
      if (!targetCard || targetCard === draggedCard) return;

      const targetBox = targetCard.getBoundingClientRect();
      const shouldPlaceAfter = event.clientY > targetBox.top + targetBox.height / 2;

      clearOverStates();
      targetCard.classList.add("is-over");

      if (shouldPlaceAfter) {
        targetCard.insertAdjacentElement("afterend", draggedCard);
      } else {
        targetCard.insertAdjacentElement("beforebegin", draggedCard);
      }

      updateOrder();
    });

    grid.addEventListener("drop", function (event) {
      event.preventDefault();
      clearOverStates();

      if (draggedCard) {
        draggedCard.classList.remove("is-dragging");
      }

      draggedCard = null;
      updateOrder();
    });

    grid.addEventListener("dragend", function () {
      clearOverStates();

      if (draggedCard) {
        draggedCard.classList.remove("is-dragging");
      }

      draggedCard = null;
      updateOrder();
    });

    updateOrder();
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initGallerySort);
  } else {
    initGallerySort();
  }
})();

HTML

<div class="vb-galsort-demo">
  <div class="vb-galsort-wrap" data-vb-galsort>
    <div class="vb-galsort-head">
      <span>Example 05</span>
      <h3>Reorder Image Gallery</h3>
      <p>Drag the small handle on any gallery card to reorder the grid. The numbers update automatically.</p>
    </div>

    <div class="vb-galsort-toolbar">
      <span>Gallery order</span>
      <strong data-vb-galsort-output>1 → 2 → 3 → 4 → 5 → 6</strong>
    </div>

    <div class="vb-galsort-grid" data-vb-galsort-grid>
      <article class="vb-galsort-card" data-vb-galsort-card>
        <span class="vb-galsort-handle" draggable="true" data-vb-galsort-handle>⋮⋮</span>
        <div class="vb-galsort-art vb-galsort-art-one"></div>
        <div class="vb-galsort-body">
          <strong data-vb-galsort-number>1</strong>
          <div>
            <h4>Hero Product Image</h4>
            <p>Main ecommerce image</p>
          </div>
        </div>
      </article>

      <article class="vb-galsort-card" data-vb-galsort-card>
        <span class="vb-galsort-handle" draggable="true" data-vb-galsort-handle>⋮⋮</span>
        <div class="vb-galsort-art vb-galsort-art-two"></div>
        <div class="vb-galsort-body">
          <strong data-vb-galsort-number>2</strong>
          <div>
            <h4>Detail Closeup</h4>
            <p>Texture and material</p>
          </div>
        </div>
      </article>

      <article class="vb-galsort-card" data-vb-galsort-card>
        <span class="vb-galsort-handle" draggable="true" data-vb-galsort-handle>⋮⋮</span>
        <div class="vb-galsort-art vb-galsort-art-three"></div>
        <div class="vb-galsort-body">
          <strong data-vb-galsort-number>3</strong>
          <div>
            <h4>Lifestyle Scene</h4>
            <p>Real usage example</p>
          </div>
        </div>
      </article>

      <article class="vb-galsort-card" data-vb-galsort-card>
        <span class="vb-galsort-handle" draggable="true" data-vb-galsort-handle>⋮⋮</span>
        <div class="vb-galsort-art vb-galsort-art-four"></div>
        <div class="vb-galsort-body">
          <strong data-vb-galsort-number>4</strong>
          <div>
            <h4>Package Preview</h4>
            <p>Box and contents</p>
          </div>
        </div>
      </article>

      <article class="vb-galsort-card" data-vb-galsort-card>
        <span class="vb-galsort-handle" draggable="true" data-vb-galsort-handle>⋮⋮</span>
        <div class="vb-galsort-art vb-galsort-art-five"></div>
        <div class="vb-galsort-body">
          <strong data-vb-galsort-number>5</strong>
          <div>
            <h4>Mobile Crop</h4>
            <p>Social preview image</p>
          </div>
        </div>
      </article>

      <article class="vb-galsort-card" data-vb-galsort-card>
        <span class="vb-galsort-handle" draggable="true" data-vb-galsort-handle>⋮⋮</span>
        <div class="vb-galsort-art vb-galsort-art-six"></div>
        <div class="vb-galsort-body">
          <strong data-vb-galsort-number>6</strong>
          <div>
            <h4>Gallery Thumbnail</h4>
            <p>Small preview card</p>
          </div>
        </div>
      </article>
    </div>
  </div>
</div>

CSS

.vb-galsort-demo,
.vb-galsort-demo * {
  box-sizing: border-box;
}

.vb-galsort-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 12% 18%, rgba(59, 130, 246, 0.18), transparent 34%),
    radial-gradient(circle at 88% 12%, rgba(16, 185, 129, 0.18), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfdf5 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-galsort-wrap {
  max-width: 1160px;
  margin: 0 auto;
}

.vb-galsort-head {
  display: grid;
  gap: 12px;
  max-width: 780px;
  margin-bottom: 20px;
}

.vb-galsort-head span {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-galsort-head h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-galsort-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-galsort-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
  padding: 14px 16px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.07);
}

.vb-galsort-toolbar span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-galsort-toolbar strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px;
  font-weight: 950;
}

.vb-galsort-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.vb-galsort-card {
  position: relative;
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 46px rgba(15, 23, 42, 0.10);
  transition: opacity 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
}

.vb-galsort-card.is-dragging {
  opacity: 0.42;
  transform: scale(0.98);
  border-color: rgba(37, 99, 235, 0.70);
}

.vb-galsort-card.is-over {
  border-color: rgba(249, 115, 22, 0.85);
}

.vb-galsort-handle {
  position: absolute;
  z-index: 5;
  top: 12px;
  left: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 15px;
  background: rgba(15, 23, 42, 0.78);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  user-select: none;
  backdrop-filter: blur(10px);
}

.vb-galsort-handle:active {
  cursor: grabbing;
}

.vb-galsort-art {
  min-height: 190px;
  position: relative;
  overflow: hidden;
}

.vb-galsort-art::before,
.vb-galsort-art::after {
  content: "";
  position: absolute;
  border-radius: 999px;
  background: rgba(255,255,255,0.30);
}

.vb-galsort-art::before {
  width: 90px;
  height: 90px;
  left: 24px;
  top: 28px;
}

.vb-galsort-art::after {
  width: 160px;
  height: 160px;
  right: -38px;
  bottom: -48px;
}

.vb-galsort-art-one {
  background: linear-gradient(135deg, #2563eb, #7c3aed);
}

.vb-galsort-art-two {
  background: linear-gradient(135deg, #db2777, #f97316);
}

.vb-galsort-art-three {
  background: linear-gradient(135deg, #059669, #0ea5e9);
}

.vb-galsort-art-four {
  background: linear-gradient(135deg, #111827, #475569);
}

.vb-galsort-art-five {
  background: linear-gradient(135deg, #7c2d12, #f59e0b);
}

.vb-galsort-art-six {
  background: linear-gradient(135deg, #4338ca, #06b6d4);
}

.vb-galsort-body {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 16px;
}

.vb-galsort-body strong {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
}

.vb-galsort-body h4 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px !important;
  line-height: 1.25 !important;
  font-weight: 900 !important;
}

.vb-galsort-body p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.35;
  font-weight: 700;
}

@media (max-width: 900px) {
  .vb-galsort-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .vb-galsort-grid {
    grid-template-columns: 1fr;
  }

  .vb-galsort-toolbar {
    display: grid;
  }
}

This JavaScript reorderable image gallery is useful for ecommerce image order tools, portfolio galleries, media libraries, product admin panels, landing page builders, and visual content management systems.

6. Drag and Drop Shopping Cart

A drag and drop shopping cart is a practical JavaScript drag and drop example for ecommerce stores, quote builders, bundle builders, product configurators, quick order tools, and interactive sales pages. Users can drag a product handle into the cart area, and JavaScript updates the cart automatically.

This example does not use the native HTML5 drag and drop API. Instead, it uses mouse and touch events, a floating ghost card, drop zone detection, quantity updates, remove buttons, and automatic cart total calculation.

Example 06

Drag and Drop Shopping Cart

Grab the orange handle and drag a product into the cart. The cart updates quantities, item count, and total price automatically.

Starter UI Kit

Reusable website sections

€29

Dashboard Widgets

Admin interface blocks

€49

Ecommerce Cards

Product and pricing UI

€39

Form Components

Inputs and validation UI

€25

JavaScript

(function () {
  function initVbdcCart() {
    const demos = document.querySelectorAll("[data-vbdc-cart]");

    demos.forEach(function (root) {
      if (root.getAttribute("data-vbdc-ready") === "1") return;
      root.setAttribute("data-vbdc-ready", "1");

      const drop = root.querySelector("[data-vbdc-drop]");
      const list = root.querySelector("[data-vbdc-list]");
      const totalEl = root.querySelector("[data-vbdc-total]");
      const countEl = root.querySelector("[data-vbdc-count]");
      const emptyEl = root.querySelector("[data-vbdc-empty]");

      let cart = [];
      let activeProduct = null;
      let ghost = null;
      let lastX = 0;
      let lastY = 0;

      function formatPrice(value) {
        return "€" + Number(value).toFixed(0);
      }

      function getPoint(event) {
        if (event.touches && event.touches.length) {
          return {
            x: event.touches[0].clientX,
            y: event.touches[0].clientY
          };
        }

        if (event.changedTouches && event.changedTouches.length) {
          return {
            x: event.changedTouches[0].clientX,
            y: event.changedTouches[0].clientY
          };
        }

        return {
          x: event.clientX,
          y: event.clientY
        };
      }

      function getProductData(product) {
        return {
          id: product.getAttribute("data-id"),
          name: product.getAttribute("data-name"),
          price: Number(product.getAttribute("data-price"))
        };
      }

      function isOverDrop(x, y) {
        const element = document.elementFromPoint(x, y);
        return !!(element && element.closest("[data-vbdc-drop]") === drop);
      }

      function renderCart() {
        list.innerHTML = "";

        let total = 0;
        let count = 0;

        cart.forEach(function (item) {
          total += item.price * item.quantity;
          count += item.quantity;

          const row = document.createElement("div");
          row.className = "vbdc-row";

          const info = document.createElement("div");
          const title = document.createElement("strong");
          const meta = document.createElement("span");
          const remove = document.createElement("button");

          title.textContent = item.name;
          meta.textContent = item.quantity + " × " + formatPrice(item.price);

          remove.type = "button";
          remove.className = "vbdc-remove";
          remove.textContent = "×";
          remove.setAttribute("aria-label", "Remove " + item.name);
          remove.setAttribute("data-vbdc-remove", item.id);

          info.appendChild(title);
          info.appendChild(meta);
          row.appendChild(info);
          row.appendChild(remove);
          list.appendChild(row);
        });

        totalEl.textContent = formatPrice(total);
        countEl.textContent = count === 1 ? "1 item" : count + " items";
        emptyEl.style.display = cart.length > 0 ? "none" : "block";
      }

      function addToCart(productData) {
        const existing = cart.find(function (item) {
          return item.id === productData.id;
        });

        if (existing) {
          existing.quantity += 1;
        } else {
          cart.push({
            id: productData.id,
            name: productData.name,
            price: productData.price,
            quantity: 1
          });
        }

        renderCart();
      }

      function createGhost(productData, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vbdc-ghost";

        const name = document.createElement("strong");
        const price = document.createElement("span");

        name.textContent = productData.name;
        price.textContent = formatPrice(productData.price);

        ghost.appendChild(name);
        ghost.appendChild(price);
        document.body.appendChild(ghost);

        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function startDrag(event) {
        const handle = event.target.closest("[data-vbdc-handle]");
        if (!handle) return;

        const product = handle.closest("[data-vbdc-product]");
        if (!product) return;

        event.preventDefault();

        const point = getPoint(event);

        activeProduct = product;
        lastX = point.x;
        lastY = point.y;

        activeProduct.classList.add("is-dragging");
        createGhost(getProductData(activeProduct), lastX, lastY);

        document.addEventListener("mousemove", moveDrag);
        document.addEventListener("mouseup", endDrag);
        document.addEventListener("touchmove", moveDrag, { passive: false });
        document.addEventListener("touchend", endDrag);
        document.addEventListener("touchcancel", cancelDrag);
      }

      function moveDrag(event) {
        if (!activeProduct) return;

        event.preventDefault();

        const point = getPoint(event);

        lastX = point.x;
        lastY = point.y;

        moveGhost(lastX, lastY);

        if (isOverDrop(lastX, lastY)) {
          drop.classList.add("is-over");
        } else {
          drop.classList.remove("is-over");
        }
      }

      function endDrag(event) {
        if (!activeProduct) return;

        const point = getPoint(event);

        lastX = point.x;
        lastY = point.y;

        if (isOverDrop(lastX, lastY)) {
          addToCart(getProductData(activeProduct));
        }

        cleanupDrag();
      }

      function cancelDrag() {
        cleanupDrag();
      }

      function cleanupDrag() {
        if (activeProduct) {
          activeProduct.classList.remove("is-dragging");
        }

        if (ghost) {
          ghost.remove();
        }

        activeProduct = null;
        ghost = null;
        drop.classList.remove("is-over");

        document.removeEventListener("mousemove", moveDrag);
        document.removeEventListener("mouseup", endDrag);
        document.removeEventListener("touchmove", moveDrag);
        document.removeEventListener("touchend", endDrag);
        document.removeEventListener("touchcancel", cancelDrag);
      }

      root.addEventListener("mousedown", startDrag);
      root.addEventListener("touchstart", startDrag, { passive: false });

      list.addEventListener("click", function (event) {
        const button = event.target.closest("[data-vbdc-remove]");
        if (!button) return;

        const id = button.getAttribute("data-vbdc-remove");

        cart = cart.filter(function (item) {
          return item.id !== id;
        });

        renderCart();
      });

      renderCart();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initVbdcCart);
  } else {
    initVbdcCart();
  }
})();

HTML

<div class="vbdc-demo">
  <div class="vbdc-wrap" data-vbdc-cart>
    <div class="vbdc-head">
      <span>Example 06</span>
      <h3>Drag and Drop Shopping Cart</h3>
      <p>Grab the orange handle and drag a product into the cart. The cart updates quantities, item count, and total price automatically.</p>
    </div>

    <div class="vbdc-layout">
      <div class="vbdc-products">
        <article class="vbdc-product" data-vbdc-product data-id="starter-kit" data-name="Starter UI Kit" data-price="29">
          <button class="vbdc-handle" type="button" data-vbdc-handle aria-label="Drag Starter UI Kit">⋮⋮</button>
          <div class="vbdc-art vbdc-art-blue"></div>
          <div class="vbdc-product-text">
            <h4>Starter UI Kit</h4>
            <p>Reusable website sections</p>
            <strong>€29</strong>
          </div>
        </article>

        <article class="vbdc-product" data-vbdc-product data-id="dashboard-widgets" data-name="Dashboard Widgets" data-price="49">
          <button class="vbdc-handle" type="button" data-vbdc-handle aria-label="Drag Dashboard Widgets">⋮⋮</button>
          <div class="vbdc-art vbdc-art-green"></div>
          <div class="vbdc-product-text">
            <h4>Dashboard Widgets</h4>
            <p>Admin interface blocks</p>
            <strong>€49</strong>
          </div>
        </article>

        <article class="vbdc-product" data-vbdc-product data-id="ecommerce-cards" data-name="Ecommerce Cards" data-price="39">
          <button class="vbdc-handle" type="button" data-vbdc-handle aria-label="Drag Ecommerce Cards">⋮⋮</button>
          <div class="vbdc-art vbdc-art-orange"></div>
          <div class="vbdc-product-text">
            <h4>Ecommerce Cards</h4>
            <p>Product and pricing UI</p>
            <strong>€39</strong>
          </div>
        </article>

        <article class="vbdc-product" data-vbdc-product data-id="form-components" data-name="Form Components" data-price="25">
          <button class="vbdc-handle" type="button" data-vbdc-handle aria-label="Drag Form Components">⋮⋮</button>
          <div class="vbdc-art vbdc-art-pink"></div>
          <div class="vbdc-product-text">
            <h4>Form Components</h4>
            <p>Inputs and validation UI</p>
            <strong>€25</strong>
          </div>
        </article>
      </div>

      <aside class="vbdc-cart">
        <div class="vbdc-cart-head">
          <div>
            <span>Drop zone</span>
            <h4>Your Cart</h4>
          </div>
          <strong data-vbdc-count>0 items</strong>
        </div>

        <div class="vbdc-drop" data-vbdc-drop>
          <p data-vbdc-empty>Drag product handles here to add items to the cart.</p>
          <div class="vbdc-list" data-vbdc-list></div>
        </div>

        <div class="vbdc-total">
          <span>Total</span>
          <strong data-vbdc-total>€0</strong>
        </div>
      </aside>
    </div>
  </div>
</div>

CSS

.vbdc-demo,
.vbdc-demo * {
  box-sizing: border-box;
}

.vbdc-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(249, 115, 22, 0.18), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(37, 99, 235, 0.18), transparent 34%),
    linear-gradient(135deg, #fff7ed 0%, #eff6ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
  overflow: hidden;
}

.vbdc-wrap {
  max-width: 1160px;
  margin: 0 auto;
}

.vbdc-head {
  max-width: 780px;
  margin-bottom: 22px;
}

.vbdc-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ffedd5;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vbdc-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vbdc-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vbdc-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 0.78fr);
  gap: 18px;
  align-items: stretch;
}

.vbdc-products {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
}

.vbdc-product {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  gap: 14px;
  align-items: center;
  min-width: 0;
  padding: 16px;
  border-radius: 24px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.25);
  box-shadow: 0 16px 44px rgba(15, 23, 42, 0.08);
  transition: opacity 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
}

.vbdc-product.is-dragging {
  opacity: 0.48;
  transform: scale(0.985);
  border-color: rgba(249, 115, 22, 0.78);
}

.vbdc-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 54px;
  border: 0;
  border-radius: 16px;
  background: #ffedd5;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 21px;
  font-weight: 950;
  cursor: grab;
  user-select: none;
  touch-action: none;
  line-height: 1;
}

.vbdc-handle:active {
  cursor: grabbing;
}

.vbdc-art {
  width: 76px;
  height: 76px;
  border-radius: 22px;
  flex: 0 0 auto;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.32);
}

.vbdc-art-blue {
  background: linear-gradient(135deg, #2563eb, #7c3aed);
}

.vbdc-art-green {
  background: linear-gradient(135deg, #059669, #0ea5e9);
}

.vbdc-art-orange {
  background: linear-gradient(135deg, #f97316, #db2777);
}

.vbdc-art-pink {
  background: linear-gradient(135deg, #be185d, #7c3aed);
}

.vbdc-product-text {
  min-width: 0;
}

.vbdc-product-text h4 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
}

.vbdc-product-text p {
  margin: 0 0 8px !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.35;
  font-weight: 700;
}

.vbdc-product-text strong {
  color: #ea580c !important;
  -webkit-text-fill-color: #ea580c !important;
  font-size: 18px;
  line-height: 1;
  font-weight: 950;
}

.vbdc-cart {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
  padding: 18px;
  border-radius: 30px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 211, 238, 0.20), transparent 36%),
    linear-gradient(135deg, #0f172a, #1e1b4b) !important;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.24);
}

.vbdc-cart-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.vbdc-cart-head span {
  display: block;
  margin-bottom: 5px;
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vbdc-cart-head h4 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 28px !important;
  line-height: 1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.05em;
}

.vbdc-cart-head > strong {
  display: inline-flex;
  min-width: 82px;
  justify-content: center;
  padding: 9px 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 950;
  white-space: nowrap;
}

.vbdc-drop {
  flex: 1;
  min-height: 260px;
  padding: 14px;
  border-radius: 24px;
  border: 1px dashed rgba(255, 255, 255, 0.32);
  background: rgba(255, 255, 255, 0.08);
  transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}

.vbdc-drop.is-over {
  border-color: rgba(251, 146, 60, 0.95);
  background: rgba(249, 115, 22, 0.18);
  box-shadow: inset 0 0 0 4px rgba(249, 115, 22, 0.14);
}

.vbdc-drop > p {
  margin: 0 !important;
  padding: 18px;
  border-radius: 18px;
  background: rgba(255, 255, 255, 0.10);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 750;
  text-align: center;
}

.vbdc-list {
  display: grid;
  gap: 10px;
}

.vbdc-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 13px;
  border-radius: 18px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.13);
}

.vbdc-row strong {
  display: block;
  margin-bottom: 4px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.25;
  font-weight: 900;
}

.vbdc-row span {
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 750;
}

.vbdc-remove {
  width: 34px;
  height: 34px;
  border: 0;
  border-radius: 999px;
  background: rgba(248, 113, 113, 0.20);
  color: #fecaca !important;
  -webkit-text-fill-color: #fecaca !important;
  font-size: 18px;
  font-weight: 950;
  cursor: pointer;
}

.vbdc-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.14);
}

.vbdc-total span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vbdc-total strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 36px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.06em;
}

.vbdc-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(280px, calc(100vw - 32px));
  padding: 14px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(249, 115, 22, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
  transform: rotate(1deg);
}

.vbdc-ghost strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  line-height: 1.25;
  font-weight: 950;
}

.vbdc-ghost span {
  display: block;
  margin-top: 4px;
  color: #ea580c !important;
  -webkit-text-fill-color: #ea580c !important;
  font-size: 14px;
  font-weight: 950;
}

@media (max-width: 900px) {
  .vbdc-layout {
    grid-template-columns: 1fr;
  }

  .vbdc-drop {
    min-height: 220px;
  }
}

@media (max-width: 640px) {
  .vbdc-products {
    grid-template-columns: 1fr;
  }

  .vbdc-product {
    grid-template-columns: auto auto minmax(0, 1fr);
    gap: 12px;
    padding: 14px;
  }

  .vbdc-art {
    width: 64px;
    height: 64px;
    border-radius: 18px;
  }

  .vbdc-cart-head {
    align-items: flex-start;
  }

  .vbdc-cart-head h4 {
    font-size: 24px !important;
  }
}

@media (max-width: 420px) {
  .vbdc-product {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .vbdc-art {
    display: none;
  }
}

This JavaScript drag and drop shopping cart is useful for ecommerce stores, quote builders, bundle builders, quick order interfaces, product configurators, landing page demos, and interactive sales tools.

Need a custom JavaScript feature?

Can’t build this yourself? We can create it for your website.

If you need a custom drag and drop tool, upload interface, product builder, dashboard widget, calculator, booking feature, or interactive JavaScript component, our team can build a responsive and professional solution for your website.

Contact Us

7. Drag Items Between Two Lists

Dragging items between two lists is one of the most practical JavaScript drag and drop patterns. It works well for task assignment, selected items, user permissions, comparison tools, booking systems, and admin dashboards where users move cards from one group to another.

Example 07

Drag Items Between Two Lists

Grab the handle and move tasks between Available Tasks and Selected Tasks.

Available Tasks

0
Design homepage hero

Visual UI task

Write product copy

Content task

Fix mobile spacing

Frontend task

Selected Tasks

0
Connect form validation

JavaScript task

Move tasks between the lists.

JavaScript

(function () {
  function initTwoListDrag() {
    document.querySelectorAll("[data-vb-two-list]").forEach(function (root) {
      if (root.getAttribute("data-vb-two-ready") === "1") return;
      root.setAttribute("data-vb-two-ready", "1");

      const drops = Array.from(root.querySelectorAll("[data-vb-two-drop]"));
      const status = root.querySelector("[data-vb-two-status]");

      let activeCard = null;
      let ghost = null;
      let lastX = 0;
      let lastY = 0;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateCounts() {
        drops.forEach(function (drop) {
          const name = drop.getAttribute("data-list");
          const count = root.querySelector('[data-vb-two-count="' + name + '"]');
          if (count) count.textContent = drop.querySelectorAll("[data-vb-two-card]").length;
        });
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-two-list-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function getDropAtPoint(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el) return null;
        const drop = el.closest("[data-vb-two-drop]");
        return drops.includes(drop) ? drop : null;
      }

      function clearOver() {
        drops.forEach(function (drop) {
          drop.classList.remove("is-over");
        });
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-two-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-two-card]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);
        activeCard = card;
        lastX = point.x;
        lastY = point.y;

        card.classList.add("is-dragging");
        createGhost(card, lastX, lastY);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!activeCard) return;

        event.preventDefault();

        const point = getPoint(event);
        lastX = point.x;
        lastY = point.y;

        moveGhost(lastX, lastY);
        clearOver();

        const drop = getDropAtPoint(lastX, lastY);
        if (drop) drop.classList.add("is-over");
      }

      function end(event) {
        if (!activeCard) return;

        const point = getPoint(event);
        lastX = point.x;
        lastY = point.y;

        const drop = getDropAtPoint(lastX, lastY);
        if (drop) {
          drop.appendChild(activeCard);
          status.textContent = activeCard.getAttribute("data-title") + " moved to " + drop.getAttribute("data-list") + ".";
        }

        cleanup();
        updateCounts();
      }

      function cancel() {
        cleanup();
        updateCounts();
      }

      function cleanup() {
        if (activeCard) activeCard.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        activeCard = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });

      updateCounts();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initTwoListDrag);
  } else {
    initTwoListDrag();
  }
})();

HTML

<div class="vb-two-list-demo">
  <div class="vb-two-list-wrap" data-vb-two-list>
    <div class="vb-two-list-head">
      <span>Example 07</span>
      <h3>Drag Items Between Two Lists</h3>
      <p>Grab the handle and move tasks between Available Tasks and Selected Tasks.</p>
    </div>

    <div class="vb-two-list-board">
      <section class="vb-two-list-column">
        <div class="vb-two-list-title">
          <h4>Available Tasks</h4>
          <strong data-vb-two-count="available">0</strong>
        </div>

        <div class="vb-two-list-drop" data-vb-two-drop data-list="available">
          <article class="vb-two-list-card" data-vb-two-card data-title="Design homepage hero">
            <button type="button" class="vb-two-list-handle" data-vb-two-handle>⋮⋮</button>
            <div>
              <h5>Design homepage hero</h5>
              <p>Visual UI task</p>
            </div>
          </article>

          <article class="vb-two-list-card" data-vb-two-card data-title="Write product copy">
            <button type="button" class="vb-two-list-handle" data-vb-two-handle>⋮⋮</button>
            <div>
              <h5>Write product copy</h5>
              <p>Content task</p>
            </div>
          </article>

          <article class="vb-two-list-card" data-vb-two-card data-title="Fix mobile spacing">
            <button type="button" class="vb-two-list-handle" data-vb-two-handle>⋮⋮</button>
            <div>
              <h5>Fix mobile spacing</h5>
              <p>Frontend task</p>
            </div>
          </article>
        </div>
      </section>

      <section class="vb-two-list-column">
        <div class="vb-two-list-title">
          <h4>Selected Tasks</h4>
          <strong data-vb-two-count="selected">0</strong>
        </div>

        <div class="vb-two-list-drop" data-vb-two-drop data-list="selected">
          <article class="vb-two-list-card" data-vb-two-card data-title="Connect form validation">
            <button type="button" class="vb-two-list-handle" data-vb-two-handle>⋮⋮</button>
            <div>
              <h5>Connect form validation</h5>
              <p>JavaScript task</p>
            </div>
          </article>
        </div>
      </section>
    </div>

    <div class="vb-two-list-status" data-vb-two-status>Move tasks between the lists.</div>
  </div>
</div>

CSS

.vb-two-list-demo,
.vb-two-list-demo * {
  box-sizing: border-box;
}

.vb-two-list-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(59, 130, 246, 0.18), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(16, 185, 129, 0.18), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfdf5 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
  overflow: hidden;
}

.vb-two-list-wrap {
  max-width: 1120px;
  margin: 0 auto;
}

.vb-two-list-head {
  max-width: 780px;
  margin-bottom: 22px;
}

.vb-two-list-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-two-list-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-two-list-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-two-list-board {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.vb-two-list-column {
  min-width: 0;
  padding: 16px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}

.vb-two-list-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.vb-two-list-title h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.04em;
}

.vb-two-list-title strong {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 38px;
  height: 34px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-two-list-drop {
  display: grid;
  gap: 12px;
  min-height: 250px;
  padding: 12px;
  border-radius: 22px;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  background: #f8fafc;
  transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}

.vb-two-list-drop.is-over {
  border-color: rgba(37, 99, 235, 0.75);
  background: #eff6ff;
  box-shadow: inset 0 0 0 4px rgba(37, 99, 235, 0.10);
}

.vb-two-list-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 13px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 12px 30px rgba(15, 23, 42, 0.07);
}

.vb-two-list-card.is-dragging {
  opacity: 0.45;
}

.vb-two-list-handle {
  width: 38px;
  height: 44px;
  border: 0;
  border-radius: 14px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  user-select: none;
  touch-action: none;
}

.vb-two-list-card h5 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
}

.vb-two-list-card p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.35;
}

.vb-two-list-status {
  margin-top: 16px;
  padding: 13px 15px;
  border-radius: 18px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-two-list-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(300px, calc(100vw - 32px));
  padding: 14px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(37, 99, 235, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
  transform: rotate(1deg);
}

.vb-two-list-ghost strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 950;
}

@media (max-width: 760px) {
  .vb-two-list-board {
    grid-template-columns: 1fr;
  }

  .vb-two-list-drop {
    min-height: 180px;
  }
}

This JavaScript drag and drop two-list interface is useful for task managers, selected item builders, permission panels, comparison tools, admin dashboards, and booking interfaces.

8. Priority List Drag and Drop Sorter

A priority list drag and drop sorter lets users reorder tasks by importance. This pattern is useful for project management tools, support queues, editorial calendars, dashboard widgets, admin panels, and productivity apps.

Example 08

Priority List Sorter

Drag the handle to reorder tasks. The first item becomes the top priority.

1

Fix checkout error

Critical ecommerce issue

High
2

Update mobile menu

Navigation improvement

Medium
3

Write FAQ section

SEO content task

Low
4

Compress hero images

Performance task

Medium

JavaScript

(function () {
  function initPrioritySort() {
    document.querySelectorAll("[data-vb-priority]").forEach(function (root) {
      if (root.getAttribute("data-vb-priority-ready") === "1") return;
      root.setAttribute("data-vb-priority-ready", "1");

      const list = root.querySelector("[data-vb-priority-list]");
      const top = root.querySelector("[data-vb-priority-top]");

      let activeItem = null;
      let ghost = null;
      let lastX = 0;
      let lastY = 0;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateList() {
        const items = Array.from(list.querySelectorAll("[data-vb-priority-item]"));
        items.forEach(function (item, index) {
          const number = item.querySelector("[data-vb-priority-number]");
          if (number) number.textContent = index + 1;
        });

        if (items[0]) {
          top.textContent = items[0].getAttribute("data-task");
        }
      }

      function createGhost(item, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-priority-ghost";
        ghost.innerHTML = "<strong>" + item.getAttribute("data-task") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function clearOver() {
        list.querySelectorAll(".is-over").forEach(function (item) {
          item.classList.remove("is-over");
        });
      }

      function reorderAtPoint(x, y) {
        const el = document.elementFromPoint(x, y);
        const target = el ? el.closest("[data-vb-priority-item]") : null;

        if (!target || target === activeItem || !list.contains(target)) return;

        const rect = target.getBoundingClientRect();
        const after = y > rect.top + rect.height / 2;

        clearOver();
        target.classList.add("is-over");

        if (after) {
          target.insertAdjacentElement("afterend", activeItem);
        } else {
          target.insertAdjacentElement("beforebegin", activeItem);
        }

        updateList();
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-priority-handle]");
        if (!handle) return;

        const item = handle.closest("[data-vb-priority-item]");
        if (!item) return;

        event.preventDefault();

        const point = getPoint(event);
        activeItem = item;
        lastX = point.x;
        lastY = point.y;

        item.classList.add("is-dragging");
        createGhost(item, lastX, lastY);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!activeItem) return;

        event.preventDefault();

        const point = getPoint(event);
        lastX = point.x;
        lastY = point.y;

        moveGhost(lastX, lastY);
        reorderAtPoint(lastX, lastY);
      }

      function end() {
        cleanup();
        updateList();
      }

      function cancel() {
        cleanup();
        updateList();
      }

      function cleanup() {
        if (activeItem) activeItem.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        clearOver();
        activeItem = null;
        ghost = null;

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });

      updateList();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initPrioritySort);
  } else {
    initPrioritySort();
  }
})();

HTML

<div class="vb-priority-demo">
  <div class="vb-priority-wrap" data-vb-priority>
    <div class="vb-priority-head">
      <span>Example 08</span>
      <h3>Priority List Sorter</h3>
      <p>Drag the handle to reorder tasks. The first item becomes the top priority.</p>
    </div>

    <div class="vb-priority-panel">
      <div class="vb-priority-list" data-vb-priority-list>
        <article class="vb-priority-item" data-vb-priority-item data-task="Fix checkout error">
          <button type="button" class="vb-priority-handle" data-vb-priority-handle>⋮⋮</button>
          <strong data-vb-priority-number>1</strong>
          <div>
            <h4>Fix checkout error</h4>
            <p>Critical ecommerce issue</p>
          </div>
          <span>High</span>
        </article>

        <article class="vb-priority-item" data-vb-priority-item data-task="Update mobile menu">
          <button type="button" class="vb-priority-handle" data-vb-priority-handle>⋮⋮</button>
          <strong data-vb-priority-number>2</strong>
          <div>
            <h4>Update mobile menu</h4>
            <p>Navigation improvement</p>
          </div>
          <span>Medium</span>
        </article>

        <article class="vb-priority-item" data-vb-priority-item data-task="Write FAQ section">
          <button type="button" class="vb-priority-handle" data-vb-priority-handle>⋮⋮</button>
          <strong data-vb-priority-number>3</strong>
          <div>
            <h4>Write FAQ section</h4>
            <p>SEO content task</p>
          </div>
          <span>Low</span>
        </article>

        <article class="vb-priority-item" data-vb-priority-item data-task="Compress hero images">
          <button type="button" class="vb-priority-handle" data-vb-priority-handle>⋮⋮</button>
          <strong data-vb-priority-number>4</strong>
          <div>
            <h4>Compress hero images</h4>
            <p>Performance task</p>
          </div>
          <span>Medium</span>
        </article>
      </div>

      <aside class="vb-priority-summary">
        <span>Top priority</span>
        <strong data-vb-priority-top>Fix checkout error</strong>
        <p>The list updates instantly when the order changes.</p>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-priority-demo,
.vb-priority-demo * {
  box-sizing: border-box;
}

.vb-priority-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(244, 63, 94, 0.16), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(249, 115, 22, 0.18), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #fff7ed 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-priority-wrap {
  max-width: 1120px;
  margin: 0 auto;
}

.vb-priority-head {
  max-width: 760px;
  margin-bottom: 22px;
}

.vb-priority-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-priority-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-priority-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-priority-panel {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(260px, 0.8fr);
  gap: 16px;
  align-items: stretch;
}

.vb-priority-list {
  display: grid;
  gap: 12px;
  min-width: 0;
  padding: 16px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}

.vb-priority-item {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 14px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
}

.vb-priority-item.is-dragging {
  opacity: 0.45;
}

.vb-priority-item.is-over {
  border-color: rgba(244, 63, 94, 0.70);
  background: #fff1f2;
}

.vb-priority-handle {
  width: 38px;
  height: 44px;
  border: 0;
  border-radius: 14px;
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  user-select: none;
  touch-action: none;
}

.vb-priority-item > strong {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-priority-item h4 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
}

.vb-priority-item p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  font-weight: 700;
}

.vb-priority-item span {
  padding: 7px 10px;
  border-radius: 999px;
  background: #fff7ed;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 12px;
  font-weight: 900;
  white-space: nowrap;
}

.vb-priority-summary {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 26px;
  border-radius: 28px;
  background:
    radial-gradient(circle at 16% 16%, rgba(251, 146, 60, 0.22), transparent 36%),
    linear-gradient(135deg, #0f172a, #7f1d1d) !important;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.20);
}

.vb-priority-summary span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-priority-summary strong {
  margin: 12px 0;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(26px, 4vw, 44px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-priority-summary p {
  margin: 0 !important;
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-priority-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(320px, calc(100vw - 32px));
  padding: 14px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(244, 63, 94, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
}

.vb-priority-ghost strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 950;
}

@media (max-width: 860px) {
  .vb-priority-panel {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-priority-item {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .vb-priority-item > strong,
  .vb-priority-item span {
    display: none;
  }
}

This JavaScript priority list sorter is useful for project dashboards, task managers, admin queues, editorial calendars, support boards, and productivity interfaces.

9. Drag and Drop Team Member Assignment Board

A team member assignment board lets users drag people into different departments, projects, or work groups. This is a useful JavaScript drag and drop pattern for HR dashboards, project management apps, team planning tools, CRM systems, and admin panels.

Example 09

Team Assignment Board

Drag team members into the correct project column. Counters update after every assignment.

Team Members

0
MI
Mia

UI Designer

LE
Leo

Frontend Developer

SA
Sara

SEO Specialist

Website Redesign

0

SEO Campaign

0
Assign team members by dragging them into a project column.

JavaScript

(function () {
  function initTeamBoard() {
    document.querySelectorAll("[data-vb-team]").forEach(function (root) {
      if (root.getAttribute("data-vb-team-ready") === "1") return;
      root.setAttribute("data-vb-team-ready", "1");

      const drops = Array.from(root.querySelectorAll("[data-vb-team-drop]"));
      const status = root.querySelector("[data-vb-team-status]");

      let activeCard = null;
      let ghost = null;
      let lastX = 0;
      let lastY = 0;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateCounts() {
        drops.forEach(function (drop) {
          const zone = drop.getAttribute("data-team-zone");
          const count = root.querySelector('[data-vb-team-count="' + zone + '"]');
          if (count) count.textContent = drop.querySelectorAll("[data-vb-team-card]").length;
        });
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-team-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-person") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function getDropAtPoint(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el) return null;
        const drop = el.closest("[data-vb-team-drop]");
        return drops.includes(drop) ? drop : null;
      }

      function clearOver() {
        drops.forEach(function (drop) {
          drop.classList.remove("is-over");
        });
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-team-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-team-card]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);

        activeCard = card;
        lastX = point.x;
        lastY = point.y;

        activeCard.classList.add("is-dragging");
        createGhost(activeCard, lastX, lastY);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!activeCard) return;

        event.preventDefault();

        const point = getPoint(event);
        lastX = point.x;
        lastY = point.y;

        moveGhost(lastX, lastY);
        clearOver();

        const drop = getDropAtPoint(lastX, lastY);
        if (drop) drop.classList.add("is-over");
      }

      function end(event) {
        if (!activeCard) return;

        const point = getPoint(event);
        lastX = point.x;
        lastY = point.y;

        const drop = getDropAtPoint(lastX, lastY);

        if (drop) {
          drop.appendChild(activeCard);
          status.textContent = activeCard.getAttribute("data-person") + " assigned to " + drop.getAttribute("data-team-zone") + ".";
        }

        cleanup();
        updateCounts();
      }

      function cancel() {
        cleanup();
        updateCounts();
      }

      function cleanup() {
        if (activeCard) activeCard.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        activeCard = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });

      updateCounts();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initTeamBoard);
  } else {
    initTeamBoard();
  }
})();

HTML

<div class="vb-team-demo">
  <div class="vb-team-wrap" data-vb-team>
    <div class="vb-team-head">
      <span>Example 09</span>
      <h3>Team Assignment Board</h3>
      <p>Drag team members into the correct project column. Counters update after every assignment.</p>
    </div>

    <div class="vb-team-board">
      <section class="vb-team-pool">
        <div class="vb-team-section-title">
          <h4>Team Members</h4>
          <strong data-vb-team-count="pool">0</strong>
        </div>

        <div class="vb-team-drop" data-vb-team-drop data-team-zone="pool">
          <article class="vb-team-card" data-vb-team-card data-person="Mia">
            <button type="button" data-vb-team-handle>⋮⋮</button>
            <span>MI</span>
            <div>
              <h5>Mia</h5>
              <p>UI Designer</p>
            </div>
          </article>

          <article class="vb-team-card" data-vb-team-card data-person="Leo">
            <button type="button" data-vb-team-handle>⋮⋮</button>
            <span>LE</span>
            <div>
              <h5>Leo</h5>
              <p>Frontend Developer</p>
            </div>
          </article>

          <article class="vb-team-card" data-vb-team-card data-person="Sara">
            <button type="button" data-vb-team-handle>⋮⋮</button>
            <span>SA</span>
            <div>
              <h5>Sara</h5>
              <p>SEO Specialist</p>
            </div>
          </article>
        </div>
      </section>

      <section class="vb-team-projects">
        <div class="vb-team-column">
          <div class="vb-team-section-title">
            <h4>Website Redesign</h4>
            <strong data-vb-team-count="redesign">0</strong>
          </div>
          <div class="vb-team-drop" data-vb-team-drop data-team-zone="redesign"></div>
        </div>

        <div class="vb-team-column">
          <div class="vb-team-section-title">
            <h4>SEO Campaign</h4>
            <strong data-vb-team-count="seo">0</strong>
          </div>
          <div class="vb-team-drop" data-vb-team-drop data-team-zone="seo"></div>
        </div>
      </section>
    </div>

    <div class="vb-team-status" data-vb-team-status>Assign team members by dragging them into a project column.</div>
  </div>
</div>

CSS

.vb-team-demo,
.vb-team-demo * {
  box-sizing: border-box;
}

.vb-team-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(124, 58, 237, 0.16), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(14, 165, 233, 0.18), transparent 34%),
    linear-gradient(135deg, #f5f3ff 0%, #ecfeff 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-team-wrap {
  max-width: 1160px;
  margin: 0 auto;
}

.vb-team-head {
  max-width: 780px;
  margin-bottom: 22px;
}

.vb-team-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-team-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-team-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-team-board {
  display: grid;
  grid-template-columns: minmax(280px, 0.8fr) minmax(0, 1.2fr);
  gap: 16px;
}

.vb-team-pool,
.vb-team-column {
  min-width: 0;
  padding: 16px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}

.vb-team-projects {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}

.vb-team-section-title {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  margin-bottom: 14px;
}

.vb-team-section-title h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.04em;
}

.vb-team-section-title strong {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 38px;
  height: 34px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-team-drop {
  display: grid;
  align-content: start;
  gap: 12px;
  min-height: 270px;
  padding: 12px;
  border-radius: 22px;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  background: #f8fafc;
}

.vb-team-drop.is-over {
  border-color: rgba(124, 58, 237, 0.78);
  background: #f5f3ff;
  box-shadow: inset 0 0 0 4px rgba(124, 58, 237, 0.10);
}

.vb-team-card {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 13px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 12px 30px rgba(15, 23, 42, 0.07);
}

.vb-team-card.is-dragging {
  opacity: 0.45;
}

.vb-team-card button {
  width: 36px;
  height: 42px;
  border: 0;
  border-radius: 14px;
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  user-select: none;
  touch-action: none;
}

.vb-team-card > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: 999px;
  background: linear-gradient(135deg, #7c3aed, #06b6d4);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-team-card h5 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
}

.vb-team-card p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  font-weight: 700;
}

.vb-team-status {
  margin-top: 16px;
  padding: 13px 15px;
  border-radius: 18px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-team-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(300px, calc(100vw - 32px));
  padding: 14px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(124, 58, 237, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
}

.vb-team-ghost strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 950;
}

@media (max-width: 940px) {
  .vb-team-board {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 700px) {
  .vb-team-projects {
    grid-template-columns: 1fr;
  }

  .vb-team-drop {
    min-height: 180px;
  }
}

@media (max-width: 420px) {
  .vb-team-card {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .vb-team-card > span {
    display: none;
  }
}

This JavaScript team assignment board is useful for HR tools, project planning dashboards, admin panels, CRM interfaces, team management apps, and task assignment systems.

10. Calendar Event Drag and Drop Planner

A calendar event drag and drop planner is useful for booking tools, editorial calendars, appointment systems, project planning dashboards, and event scheduling interfaces. Users can drag events into different day columns and instantly see how the schedule changes.

Example 10

Calendar Event Planner

Drag each event card into a weekday column to build a simple visual schedule.

Monday

0

Tuesday

0

Wednesday

0

Thursday

0

Friday

0
Drag an event into a day column.

JavaScript

(function () {
  function initCalendarPlanner() {
    document.querySelectorAll("[data-vb-calplan]").forEach(function (root) {
      if (root.getAttribute("data-vb-calplan-ready") === "1") return;
      root.setAttribute("data-vb-calplan-ready", "1");

      const drops = Array.from(root.querySelectorAll("[data-vb-calplan-drop]"));
      const status = root.querySelector("[data-vb-calplan-status]");
      let activeEvent = null;
      let ghost = null;
      let lastX = 0;
      let lastY = 0;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateCounts() {
        drops.forEach(function (drop) {
          const day = drop.getAttribute("data-day");
          const count = root.querySelector('[data-vb-calplan-count="' + day + '"]');
          if (count) count.textContent = drop.querySelectorAll("[data-vb-calplan-event]").length;
        });
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-calplan-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function getDropAtPoint(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el) return null;
        const drop = el.closest("[data-vb-calplan-drop]");
        return drops.includes(drop) ? drop : null;
      }

      function clearOver() {
        drops.forEach(function (drop) {
          drop.classList.remove("is-over");
        });
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-calplan-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-calplan-event]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);
        activeEvent = card;
        lastX = point.x;
        lastY = point.y;

        activeEvent.classList.add("is-dragging");
        createGhost(activeEvent, lastX, lastY);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!activeEvent) return;

        event.preventDefault();

        const point = getPoint(event);
        lastX = point.x;
        lastY = point.y;

        moveGhost(lastX, lastY);
        clearOver();

        const drop = getDropAtPoint(lastX, lastY);
        if (drop) drop.classList.add("is-over");
      }

      function end(event) {
        if (!activeEvent) return;

        const point = getPoint(event);
        const drop = getDropAtPoint(point.x, point.y);

        if (drop) {
          drop.appendChild(activeEvent);
          status.textContent = activeEvent.getAttribute("data-title") + " moved to " + drop.getAttribute("data-day") + ".";
        }

        cleanup();
        updateCounts();
      }

      function cancel() {
        cleanup();
        updateCounts();
      }

      function cleanup() {
        if (activeEvent) activeEvent.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        activeEvent = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateCounts();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initCalendarPlanner);
  } else {
    initCalendarPlanner();
  }
})();

HTML

<div class="vb-calplan-demo">
  <div class="vb-calplan-wrap" data-vb-calplan>
    <div class="vb-calplan-head">
      <span>Example 10</span>
      <h3>Calendar Event Planner</h3>
      <p>Drag each event card into a weekday column to build a simple visual schedule.</p>
    </div>

    <div class="vb-calplan-grid">
      <aside class="vb-calplan-sidebar">
        <div class="vb-calplan-sidebar-top">
          <span>Unscheduled</span>
          <strong data-vb-calplan-count="unscheduled">0</strong>
        </div>

        <div class="vb-calplan-drop vb-calplan-pool" data-vb-calplan-drop data-day="unscheduled">
          <article class="vb-calplan-event" data-vb-calplan-event data-title="Client Call">
            <button type="button" data-vb-calplan-handle>⋮⋮</button>
            <div>
              <strong>Client Call</strong>
              <span>30 min meeting</span>
            </div>
          </article>

          <article class="vb-calplan-event" data-vb-calplan-event data-title="Publish Blog Post">
            <button type="button" data-vb-calplan-handle>⋮⋮</button>
            <div>
              <strong>Publish Blog Post</strong>
              <span>Content task</span>
            </div>
          </article>

          <article class="vb-calplan-event" data-vb-calplan-event data-title="Design Review">
            <button type="button" data-vb-calplan-handle>⋮⋮</button>
            <div>
              <strong>Design Review</strong>
              <span>UI feedback</span>
            </div>
          </article>
        </div>
      </aside>

      <section class="vb-calplan-calendar">
        <div class="vb-calplan-day">
          <div class="vb-calplan-day-head"><h4>Monday</h4><span data-vb-calplan-count="monday">0</span></div>
          <div class="vb-calplan-drop" data-vb-calplan-drop data-day="monday"></div>
        </div>

        <div class="vb-calplan-day">
          <div class="vb-calplan-day-head"><h4>Tuesday</h4><span data-vb-calplan-count="tuesday">0</span></div>
          <div class="vb-calplan-drop" data-vb-calplan-drop data-day="tuesday"></div>
        </div>

        <div class="vb-calplan-day">
          <div class="vb-calplan-day-head"><h4>Wednesday</h4><span data-vb-calplan-count="wednesday">0</span></div>
          <div class="vb-calplan-drop" data-vb-calplan-drop data-day="wednesday"></div>
        </div>

        <div class="vb-calplan-day">
          <div class="vb-calplan-day-head"><h4>Thursday</h4><span data-vb-calplan-count="thursday">0</span></div>
          <div class="vb-calplan-drop" data-vb-calplan-drop data-day="thursday"></div>
        </div>

        <div class="vb-calplan-day">
          <div class="vb-calplan-day-head"><h4>Friday</h4><span data-vb-calplan-count="friday">0</span></div>
          <div class="vb-calplan-drop" data-vb-calplan-drop data-day="friday"></div>
        </div>
      </section>
    </div>

    <div class="vb-calplan-status" data-vb-calplan-status>Drag an event into a day column.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript calendar drag and drop planner is useful for booking systems, event planners, appointment tools, editorial calendars, project boards, and scheduling dashboards.

11. Drag and Drop Form Builder

A drag and drop form builder lets users build a simple form layout by dragging field types into a preview area. This is useful for form plugins, admin dashboards, survey builders, quote request tools, signup builders, and no-code website editors.

Example 11

Drag and Drop Form Builder

Drag a field type from the left panel into the form preview area.

Live preview

Quote Request Form

0 fields

Drag field blocks here to build the form.

Choose a field block and drag it into the form preview.

JavaScript

(function () {
  function initFormBuilder() {
    document.querySelectorAll("[data-vb-formbuild]").forEach(function (root) {
      if (root.getAttribute("data-vb-formbuild-ready") === "1") return;
      root.setAttribute("data-vb-formbuild-ready", "1");

      const drop = root.querySelector("[data-vb-formbuild-drop]");
      const fields = root.querySelector("[data-vb-formbuild-fields]");
      const empty = root.querySelector("[data-vb-formbuild-empty]");
      const count = root.querySelector("[data-vb-formbuild-count]");
      const status = root.querySelector("[data-vb-formbuild-status]");

      let activeTool = null;
      let ghost = null;
      let fieldCount = 0;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function isOverDrop(x, y) {
        const el = document.elementFromPoint(x, y);
        return !!(el && el.closest("[data-vb-formbuild-drop]") === drop);
      }

      function updateCount() {
        const total = fields.querySelectorAll(".vb-formbuild-field").length;
        count.textContent = total === 1 ? "1 field" : total + " fields";
        empty.style.display = total ? "none" : "block";
      }

      function createField(type, label) {
        fieldCount += 1;

        const block = document.createElement("div");
        block.className = "vb-formbuild-field";

        const fieldLabel = document.createElement("label");
        fieldLabel.textContent = label + " " + fieldCount;

        let input;

        if (type === "select") {
          input = document.createElement("select");
          input.innerHTML = "<option>Option One</option><option>Option Two</option>";
        } else if (type === "textarea") {
          input = document.createElement("textarea");
          input.placeholder = "Write your message...";
        } else {
          input = document.createElement("input");
          input.type = type === "email" ? "email" : "text";
          input.placeholder = label;
        }

        block.appendChild(fieldLabel);
        block.appendChild(input);
        fields.appendChild(block);

        status.textContent = label + " added to the form preview.";
        updateCount();
      }

      function createGhost(tool, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-formbuild-ghost";
        ghost.innerHTML = "<strong>" + tool.getAttribute("data-label") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const tool = event.target.closest("[data-vb-formbuild-tool]");
        if (!tool) return;

        event.preventDefault();

        const point = getPoint(event);
        activeTool = tool;
        activeTool.classList.add("is-dragging");

        createGhost(tool, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!activeTool) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);

        if (isOverDrop(point.x, point.y)) {
          drop.classList.add("is-over");
        } else {
          drop.classList.remove("is-over");
        }
      }

      function end(event) {
        if (!activeTool) return;

        const point = getPoint(event);

        if (isOverDrop(point.x, point.y)) {
          createField(activeTool.getAttribute("data-type"), activeTool.getAttribute("data-label"));
        }

        cleanup();
      }

      function cancel() {
        cleanup();
      }

      function cleanup() {
        if (activeTool) activeTool.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        activeTool = null;
        ghost = null;
        drop.classList.remove("is-over");

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateCount();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initFormBuilder);
  } else {
    initFormBuilder();
  }
})();

HTML

<div class="vb-formbuild-demo">
  <div class="vb-formbuild-wrap" data-vb-formbuild>
    <div class="vb-formbuild-head">
      <span>Example 11</span>
      <h3>Drag and Drop Form Builder</h3>
      <p>Drag a field type from the left panel into the form preview area.</p>
    </div>

    <div class="vb-formbuild-builder">
      <aside class="vb-formbuild-tools">
        <h4>Field blocks</h4>

        <button type="button" class="vb-formbuild-tool" data-vb-formbuild-tool data-type="text" data-label="Text Input">
          <span>TXT</span>
          <strong>Text Input</strong>
        </button>

        <button type="button" class="vb-formbuild-tool" data-vb-formbuild-tool data-type="email" data-label="Email Field">
          <span>@</span>
          <strong>Email Field</strong>
        </button>

        <button type="button" class="vb-formbuild-tool" data-vb-formbuild-tool data-type="select" data-label="Select Dropdown">
          <span>SEL</span>
          <strong>Select Dropdown</strong>
        </button>

        <button type="button" class="vb-formbuild-tool" data-vb-formbuild-tool data-type="textarea" data-label="Message Box">
          <span>MSG</span>
          <strong>Message Box</strong>
        </button>
      </aside>

      <section class="vb-formbuild-preview">
        <div class="vb-formbuild-preview-head">
          <div>
            <span>Live preview</span>
            <h4>Quote Request Form</h4>
          </div>
          <strong data-vb-formbuild-count>0 fields</strong>
        </div>

        <div class="vb-formbuild-drop" data-vb-formbuild-drop>
          <p data-vb-formbuild-empty>Drag field blocks here to build the form.</p>
          <div class="vb-formbuild-fields" data-vb-formbuild-fields></div>
        </div>
      </section>
    </div>

    <div class="vb-formbuild-status" data-vb-formbuild-status>Choose a field block and drag it into the form preview.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript drag and drop form builder is useful for form plugins, survey tools, quote request builders, admin dashboards, signup forms, lead generation systems, and no-code editors.

12. Page Section Builder with Drag and Drop

A page section builder with drag and drop shows how users can build a landing page layout by dragging section blocks into a page preview. This pattern is useful for website builders, landing page tools, block editors, portfolio builders, and content management dashboards.

Example 12

Page Section Builder

Drag section blocks into the page canvas to build a small landing page layout.

Page canvas

Landing Page Draft

0 sections

Drop page sections here.

Drag a section into the canvas.

JavaScript

(function () {
  function initPageBuilder() {
    document.querySelectorAll("[data-vb-pagebuild]").forEach(function (root) {
      if (root.getAttribute("data-vb-pagebuild-ready") === "1") return;
      root.setAttribute("data-vb-pagebuild-ready", "1");

      const drop = root.querySelector("[data-vb-pagebuild-drop]");
      const sections = root.querySelector("[data-vb-pagebuild-sections]");
      const empty = root.querySelector("[data-vb-pagebuild-empty]");
      const count = root.querySelector("[data-vb-pagebuild-count]");
      const status = root.querySelector("[data-vb-pagebuild-status]");

      let activeTool = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function isOverDrop(x, y) {
        const el = document.elementFromPoint(x, y);
        return !!(el && el.closest("[data-vb-pagebuild-drop]") === drop);
      }

      function updateCount() {
        const total = sections.querySelectorAll(".vb-pagebuild-section").length;
        count.textContent = total === 1 ? "1 section" : total + " sections";
        empty.style.display = total ? "none" : "block";
      }

      function sectionText(type) {
        const data = {
          hero: ["Hero Section", "A bold opening section with a headline, intro text, and call to action."],
          features: ["Feature Grid", "A section for showing benefits, services, features, or product highlights."],
          testimonial: ["Testimonial", "A social proof section for reviews, quotes, customer stories, or trust signals."],
          cta: ["CTA Section", "A conversion section that asks the visitor to contact, subscribe, buy, or request a quote."]
        };

        return data[type] || ["Page Section", "A reusable page block."];
      }

      function createSection(type) {
        const text = sectionText(type);

        const section = document.createElement("article");
        section.className = "vb-pagebuild-section " + type;

        const preview = document.createElement("div");
        preview.className = "vb-pagebuild-section-preview";

        const title = document.createElement("h5");
        title.textContent = text[0];

        const paragraph = document.createElement("p");
        paragraph.textContent = text[1];

        preview.appendChild(title);
        preview.appendChild(paragraph);
        section.appendChild(preview);
        sections.appendChild(section);

        status.textContent = text[0] + " added to the page canvas.";
        updateCount();
      }

      function createGhost(tool, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-pagebuild-ghost";
        ghost.innerHTML = "<strong>" + tool.getAttribute("data-label") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const tool = event.target.closest("[data-vb-pagebuild-tool]");
        if (!tool) return;

        event.preventDefault();

        const point = getPoint(event);
        activeTool = tool;
        activeTool.classList.add("is-dragging");

        createGhost(tool, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!activeTool) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);

        if (isOverDrop(point.x, point.y)) {
          drop.classList.add("is-over");
        } else {
          drop.classList.remove("is-over");
        }
      }

      function end(event) {
        if (!activeTool) return;

        const point = getPoint(event);

        if (isOverDrop(point.x, point.y)) {
          createSection(activeTool.getAttribute("data-type"));
        }

        cleanup();
      }

      function cancel() {
        cleanup();
      }

      function cleanup() {
        if (activeTool) activeTool.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        activeTool = null;
        ghost = null;
        drop.classList.remove("is-over");

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateCount();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initPageBuilder);
  } else {
    initPageBuilder();
  }
})();

HTML

<div class="vb-pagebuild-demo">
  <div class="vb-pagebuild-wrap" data-vb-pagebuild>
    <div class="vb-pagebuild-head">
      <span>Example 12</span>
      <h3>Page Section Builder</h3>
      <p>Drag section blocks into the page canvas to build a small landing page layout.</p>
    </div>

    <div class="vb-pagebuild-app">
      <aside class="vb-pagebuild-library">
        <h4>Sections</h4>

        <button type="button" data-vb-pagebuild-tool data-type="hero" data-label="Hero Section">
          <span>Hero</span>
          <strong>Hero Section</strong>
        </button>

        <button type="button" data-vb-pagebuild-tool data-type="features" data-label="Feature Grid">
          <span>Grid</span>
          <strong>Feature Grid</strong>
        </button>

        <button type="button" data-vb-pagebuild-tool data-type="testimonial" data-label="Testimonial">
          <span>Quote</span>
          <strong>Testimonial</strong>
        </button>

        <button type="button" data-vb-pagebuild-tool data-type="cta" data-label="CTA Section">
          <span>CTA</span>
          <strong>CTA Section</strong>
        </button>
      </aside>

      <section class="vb-pagebuild-canvas-shell">
        <div class="vb-pagebuild-canvas-head">
          <div>
            <span>Page canvas</span>
            <h4>Landing Page Draft</h4>
          </div>
          <strong data-vb-pagebuild-count>0 sections</strong>
        </div>

        <div class="vb-pagebuild-canvas" data-vb-pagebuild-drop>
          <p data-vb-pagebuild-empty>Drop page sections here.</p>
          <div class="vb-pagebuild-sections" data-vb-pagebuild-sections></div>
        </div>
      </section>
    </div>

    <div class="vb-pagebuild-status" data-vb-pagebuild-status>Drag a section into the canvas.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript page section builder is useful for landing page builders, block editors, content management dashboards, portfolio tools, website builders, and no-code page creation interfaces.

Need a custom interactive JavaScript feature? We can build drag and drop tools, calculators, forms, dashboards, and custom website components for your business.

Contact us

13. Drag and Drop Dashboard Widget Layout

A drag and drop dashboard widget layout is useful for admin dashboards, analytics panels, SaaS apps, CRM systems, reporting tools, and user profile dashboards. Users can reorder widgets to customize their own workspace.

Example 13

Dashboard Widget Layout

Drag widget handles to rearrange the dashboard layout. The order updates instantly.

Current layout Revenue → Visitors → Tasks → Server
Revenue

€24,860

Monthly sales performance

Visitors

48.2K

Website traffic

Tasks

17

Open project tasks

Server

99.9%

Uptime this month

JavaScript

(function () {
  function initDashboardGrid() {
    document.querySelectorAll("[data-vb-dashgrid]").forEach(function (root) {
      if (root.getAttribute("data-vb-dashgrid-ready") === "1") return;
      root.setAttribute("data-vb-dashgrid-ready", "1");

      const board = root.querySelector("[data-vb-dashgrid-board]");
      const output = root.querySelector("[data-vb-dashgrid-output]");
      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateOutput() {
        output.textContent = Array.from(board.querySelectorAll("[data-vb-dashgrid-widget]"))
          .map(function (item) { return item.getAttribute("data-title"); })
          .join(" → ");
      }

      function createGhost(widget, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-dashgrid-ghost";
        ghost.innerHTML = "<strong>" + widget.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function clearOver() {
        board.querySelectorAll(".is-over").forEach(function (item) {
          item.classList.remove("is-over");
        });
      }

      function reorderAt(x, y) {
        const el = document.elementFromPoint(x, y);
        const target = el ? el.closest("[data-vb-dashgrid-widget]") : null;
        if (!target || target === active || !board.contains(target)) return;

        const rect = target.getBoundingClientRect();
        const after = y > rect.top + rect.height / 2;

        clearOver();
        target.classList.add("is-over");

        if (after) {
          target.insertAdjacentElement("afterend", active);
        } else {
          target.insertAdjacentElement("beforebegin", active);
        }

        updateOutput();
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-dashgrid-handle]");
        if (!handle) return;

        const widget = handle.closest("[data-vb-dashgrid-widget]");
        if (!widget) return;

        event.preventDefault();

        const point = getPoint(event);
        active = widget;
        active.classList.add("is-dragging");
        createGhost(widget, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;
        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        reorderAt(point.x, point.y);
      }

      function end() {
        cleanup();
        updateOutput();
      }

      function cancel() {
        cleanup();
        updateOutput();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateOutput();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initDashboardGrid);
  } else {
    initDashboardGrid();
  }
})();

HTML

<div class="vb-dashgrid-demo">
  <div class="vb-dashgrid-wrap" data-vb-dashgrid>
    <div class="vb-dashgrid-head">
      <span>Example 13</span>
      <h3>Dashboard Widget Layout</h3>
      <p>Drag widget handles to rearrange the dashboard layout. The order updates instantly.</p>
    </div>

    <div class="vb-dashgrid-toolbar">
      <span>Current layout</span>
      <strong data-vb-dashgrid-output>Revenue → Visitors → Tasks → Server</strong>
    </div>

    <div class="vb-dashgrid-board" data-vb-dashgrid-board>
      <article class="vb-dashgrid-widget vb-dashgrid-wide" data-vb-dashgrid-widget data-title="Revenue">
        <button type="button" data-vb-dashgrid-handle>⋮⋮</button>
        <span>Revenue</span>
        <h4>€24,860</h4>
        <p>Monthly sales performance</p>
      </article>

      <article class="vb-dashgrid-widget" data-vb-dashgrid-widget data-title="Visitors">
        <button type="button" data-vb-dashgrid-handle>⋮⋮</button>
        <span>Visitors</span>
        <h4>48.2K</h4>
        <p>Website traffic</p>
      </article>

      <article class="vb-dashgrid-widget" data-vb-dashgrid-widget data-title="Tasks">
        <button type="button" data-vb-dashgrid-handle>⋮⋮</button>
        <span>Tasks</span>
        <h4>17</h4>
        <p>Open project tasks</p>
      </article>

      <article class="vb-dashgrid-widget vb-dashgrid-dark" data-vb-dashgrid-widget data-title="Server">
        <button type="button" data-vb-dashgrid-handle>⋮⋮</button>
        <span>Server</span>
        <h4>99.9%</h4>
        <p>Uptime this month</p>
      </article>
    </div>
  </div>
</div>

CSS

.vb-dashgrid-demo,
.vb-dashgrid-demo * {
  box-sizing: border-box;
}

.vb-dashgrid-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 10% 12%, rgba(37, 99, 235, 0.16), transparent 34%),
    radial-gradient(circle at 92% 20%, rgba(168, 85, 247, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #faf5ff 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-dashgrid-wrap {
  max-width: 1160px;
  margin: 0 auto;
}

.vb-dashgrid-head {
  max-width: 780px;
  margin-bottom: 22px;
}

.vb-dashgrid-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-dashgrid-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-dashgrid-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-dashgrid-toolbar {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: center;
  margin-bottom: 16px;
  padding: 14px 16px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.07);
}

.vb-dashgrid-toolbar span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.vb-dashgrid-toolbar strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px;
  font-weight: 950;
}

.vb-dashgrid-board {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.vb-dashgrid-widget {
  position: relative;
  min-height: 190px;
  padding: 18px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.08);
  overflow: hidden;
}

.vb-dashgrid-wide {
  grid-column: span 2;
  background:
    radial-gradient(circle at 85% 15%, rgba(37, 99, 235, 0.18), transparent 34%),
    #ffffff;
}

.vb-dashgrid-dark {
  background:
    radial-gradient(circle at 20% 10%, rgba(34, 211, 238, 0.22), transparent 34%),
    linear-gradient(135deg, #0f172a, #312e81) !important;
}

.vb-dashgrid-widget.is-dragging {
  opacity: 0.45;
}

.vb-dashgrid-widget.is-over {
  outline: 3px solid rgba(37, 99, 235, 0.28);
}

.vb-dashgrid-widget button {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 38px;
  height: 42px;
  border: 0;
  border-radius: 14px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 19px;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
}

.vb-dashgrid-widget span {
  display: inline-flex;
  margin-bottom: 24px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.vb-dashgrid-widget h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(30px, 5vw, 48px) !important;
  line-height: 1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-dashgrid-widget p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 700;
}

.vb-dashgrid-dark span,
.vb-dashgrid-dark h4,
.vb-dashgrid-dark p {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-dashgrid-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(280px, calc(100vw - 32px));
  padding: 14px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(37, 99, 235, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
}

.vb-dashgrid-ghost strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 950;
}

@media (max-width: 940px) {
  .vb-dashgrid-board {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-dashgrid-wide {
    grid-column: span 1;
  }
}

@media (max-width: 560px) {
  .vb-dashgrid-board {
    grid-template-columns: 1fr;
  }

  .vb-dashgrid-toolbar {
    align-items: flex-start;
    flex-direction: column;
  }
}

This JavaScript dashboard widget layout is useful for analytics dashboards, SaaS admin panels, reporting tools, CRM interfaces, and customizable user workspaces.

14. Nested Drag and Drop Menu Builder

A nested drag and drop menu builder lets users move menu items into parent sections. This is useful for website navigation builders, WordPress-style menus, app sidebars, documentation menus, admin panels, and no-code content tools.

Example 14

Nested Menu Builder

Drag loose menu items into parent menu groups to create a nested navigation structure.

Services

0

Company

0

Resources

0
Build a nested menu by dragging items into groups.

JavaScript

(function () {
  function initNestedMenu() {
    document.querySelectorAll("[data-vb-nestmenu]").forEach(function (root) {
      if (root.getAttribute("data-vb-nestmenu-ready") === "1") return;
      root.setAttribute("data-vb-nestmenu-ready", "1");

      const drops = Array.from(root.querySelectorAll("[data-vb-nestmenu-drop]"));
      const summary = root.querySelector("[data-vb-nestmenu-summary]");
      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateCounts() {
        drops.forEach(function (drop) {
          const zone = drop.getAttribute("data-zone");
          const count = root.querySelector('[data-vb-nestmenu-count="' + zone + '"]');
          if (count) count.textContent = drop.querySelectorAll("[data-vb-nestmenu-card]").length;
        });
      }

      function createSummary() {
        const groups = ["services", "company", "resources"].map(function (zone) {
          const drop = root.querySelector('[data-zone="' + zone + '"]');
          const labels = Array.from(drop.querySelectorAll("[data-vb-nestmenu-card]")).map(function (card) {
            return card.getAttribute("data-label");
          });
          return zone + ": " + (labels.length ? labels.join(", ") : "empty");
        });

        summary.textContent = groups.join(" | ");
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-nestmenu-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-label") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function getDropAt(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el) return null;
        const drop = el.closest("[data-vb-nestmenu-drop]");
        return drops.includes(drop) ? drop : null;
      }

      function clearOver() {
        drops.forEach(function (drop) { drop.classList.remove("is-over"); });
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-nestmenu-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-nestmenu-card]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);
        active = card;
        active.classList.add("is-dragging");
        createGhost(card, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;
        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        clearOver();

        const drop = getDropAt(point.x, point.y);
        if (drop) drop.classList.add("is-over");
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);
        const drop = getDropAt(point.x, point.y);

        if (drop) {
          drop.appendChild(active);
        }

        cleanup();
        updateCounts();
        createSummary();
      }

      function cancel() {
        cleanup();
        updateCounts();
        createSummary();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });

      updateCounts();
      createSummary();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initNestedMenu);
  } else {
    initNestedMenu();
  }
})();

HTML

<div class="vb-nestmenu-demo">
  <div class="vb-nestmenu-wrap" data-vb-nestmenu>
    <div class="vb-nestmenu-head">
      <span>Example 14</span>
      <h3>Nested Menu Builder</h3>
      <p>Drag loose menu items into parent menu groups to create a nested navigation structure.</p>
    </div>

    <div class="vb-nestmenu-app">
      <aside class="vb-nestmenu-palette">
        <h4>Menu Items</h4>

        <div class="vb-nestmenu-drop" data-vb-nestmenu-drop data-zone="items">
          <article class="vb-nestmenu-card" data-vb-nestmenu-card data-label="Pricing">
            <button type="button" data-vb-nestmenu-handle>⋮⋮</button>
            <strong>Pricing</strong>
          </article>

          <article class="vb-nestmenu-card" data-vb-nestmenu-card data-label="Portfolio">
            <button type="button" data-vb-nestmenu-handle>⋮⋮</button>
            <strong>Portfolio</strong>
          </article>

          <article class="vb-nestmenu-card" data-vb-nestmenu-card data-label="Support">
            <button type="button" data-vb-nestmenu-handle>⋮⋮</button>
            <strong>Support</strong>
          </article>
        </div>
      </aside>

      <section class="vb-nestmenu-tree">
        <div class="vb-nestmenu-group">
          <div class="vb-nestmenu-group-title">
            <h4>Services</h4>
            <span data-vb-nestmenu-count="services">0</span>
          </div>
          <div class="vb-nestmenu-subdrop" data-vb-nestmenu-drop data-zone="services"></div>
        </div>

        <div class="vb-nestmenu-group">
          <div class="vb-nestmenu-group-title">
            <h4>Company</h4>
            <span data-vb-nestmenu-count="company">0</span>
          </div>
          <div class="vb-nestmenu-subdrop" data-vb-nestmenu-drop data-zone="company"></div>
        </div>

        <div class="vb-nestmenu-group">
          <div class="vb-nestmenu-group-title">
            <h4>Resources</h4>
            <span data-vb-nestmenu-count="resources">0</span>
          </div>
          <div class="vb-nestmenu-subdrop" data-vb-nestmenu-drop data-zone="resources"></div>
        </div>
      </section>
    </div>

    <div class="vb-nestmenu-summary" data-vb-nestmenu-summary>Build a nested menu by dragging items into groups.</div>
  </div>
</div>

CSS

.vb-nestmenu-demo,
.vb-nestmenu-demo * {
  box-sizing: border-box;
}

.vb-nestmenu-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 10% 12%, rgba(20, 184, 166, 0.17), transparent 34%),
    radial-gradient(circle at 92% 20%, rgba(59, 130, 246, 0.17), transparent 34%),
    linear-gradient(135deg, #f0fdfa 0%, #eff6ff 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-nestmenu-wrap {
  max-width: 1120px;
  margin: 0 auto;
}

.vb-nestmenu-head {
  max-width: 780px;
  margin-bottom: 22px;
}

.vb-nestmenu-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-nestmenu-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-nestmenu-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-nestmenu-app {
  display: grid;
  grid-template-columns: minmax(250px, 0.38fr) minmax(0, 1fr);
  gap: 16px;
}

.vb-nestmenu-palette,
.vb-nestmenu-tree {
  padding: 16px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}

.vb-nestmenu-palette h4,
.vb-nestmenu-group-title h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.04em;
}

.vb-nestmenu-palette h4 {
  margin-bottom: 14px !important;
}

.vb-nestmenu-drop,
.vb-nestmenu-subdrop {
  display: grid;
  align-content: start;
  gap: 10px;
  min-height: 200px;
  padding: 12px;
  border-radius: 22px;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  background: #f8fafc;
}

.vb-nestmenu-subdrop {
  min-height: 120px;
}

.vb-nestmenu-drop.is-over,
.vb-nestmenu-subdrop.is-over {
  border-color: rgba(20, 184, 166, 0.82);
  background: #f0fdfa;
  box-shadow: inset 0 0 0 4px rgba(20, 184, 166, 0.10);
}

.vb-nestmenu-tree {
  display: grid;
  gap: 14px;
}

.vb-nestmenu-group {
  padding: 14px;
  border-radius: 24px;
  background: linear-gradient(135deg, #ffffff, #f8fafc);
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-nestmenu-group-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 12px;
}

.vb-nestmenu-group-title span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: 32px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
  font-weight: 950;
}

.vb-nestmenu-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  padding: 12px;
  border-radius: 16px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
}

.vb-nestmenu-card.is-dragging {
  opacity: 0.45;
}

.vb-nestmenu-card button {
  width: 34px;
  height: 40px;
  border: 0;
  border-radius: 13px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 18px;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
}

.vb-nestmenu-card strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px;
  font-weight: 950;
}

.vb-nestmenu-summary {
  margin-top: 16px;
  padding: 13px 15px;
  border-radius: 18px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-nestmenu-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(260px, calc(100vw - 32px));
  padding: 13px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(20, 184, 166, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
}

.vb-nestmenu-ghost strong {
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 14px;
  font-weight: 950;
}

@media (max-width: 860px) {
  .vb-nestmenu-app {
    grid-template-columns: 1fr;
  }

  .vb-nestmenu-drop,
  .vb-nestmenu-subdrop {
    min-height: 130px;
  }
}

This JavaScript nested menu builder is useful for navigation builders, WordPress-style menu editors, app sidebars, documentation menus, admin panels, and content management dashboards.

15. Drag and Drop FAQ Order Manager

A drag and drop FAQ order manager is useful when admins need to change the order of FAQ items without editing code. This pattern works well for help centers, product pages, support portals, documentation pages, ecommerce pages, and SEO FAQ sections.

Example 15

FAQ Order Manager

Drag FAQ handles to change the order. Click a question to preview the answer.

1

How does drag and drop work?

JavaScript tracks the dragged item, follows the pointer position, and updates the order when the user moves over another item.

2

Can this work on mobile?

Yes. This demo uses both mouse and touch events, so it can work on desktop, tablet, and mobile layouts.

3

Why use FAQ ordering?

FAQ ordering helps place the most important customer questions higher on the page for better usability and SEO.

4

Is this useful for admin panels?

Yes. Drag and drop ordering is especially useful for admin interfaces where users manage visible frontend content.

JavaScript

(function () {
  function initFaqOrder() {
    document.querySelectorAll("[data-vb-faqorder]").forEach(function (root) {
      if (root.getAttribute("data-vb-faqorder-ready") === "1") return;
      root.setAttribute("data-vb-faqorder-ready", "1");

      const list = root.querySelector("[data-vb-faqorder-list]");
      const output = root.querySelector("[data-vb-faqorder-output]");
      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateOrder() {
        const items = Array.from(list.querySelectorAll("[data-vb-faqorder-item]"));

        items.forEach(function (item, index) {
          const number = item.querySelector("[data-vb-faqorder-number]");
          if (number) number.textContent = index + 1;
        });

        if (items[0]) {
          output.textContent = "1. " + items[0].getAttribute("data-title");
        }
      }

      function createGhost(item, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-faqorder-ghost";
        ghost.innerHTML = "<strong>" + item.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function clearOver() {
        list.querySelectorAll(".is-over").forEach(function (item) {
          item.classList.remove("is-over");
        });
      }

      function reorderAt(x, y) {
        const el = document.elementFromPoint(x, y);
        const target = el ? el.closest("[data-vb-faqorder-item]") : null;

        if (!target || target === active || !list.contains(target)) return;

        const rect = target.getBoundingClientRect();
        const after = y > rect.top + rect.height / 2;

        clearOver();
        target.classList.add("is-over");

        if (after) {
          target.insertAdjacentElement("afterend", active);
        } else {
          target.insertAdjacentElement("beforebegin", active);
        }

        updateOrder();
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-faqorder-handle]");
        if (!handle) return;

        const item = handle.closest("[data-vb-faqorder-item]");
        if (!item) return;

        event.preventDefault();

        const point = getPoint(event);
        active = item;
        active.classList.add("is-dragging");
        createGhost(item, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        reorderAt(point.x, point.y);
      }

      function end() {
        cleanup();
        updateOrder();
      }

      function cancel() {
        cleanup();
        updateOrder();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });

      root.addEventListener("click", function (event) {
        const toggle = event.target.closest("[data-vb-faqorder-toggle]");
        if (!toggle) return;

        const item = toggle.closest("[data-vb-faqorder-item]");
        if (!item) return;

        item.classList.toggle("is-open");
      });

      updateOrder();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initFaqOrder);
  } else {
    initFaqOrder();
  }
})();

HTML

<div class="vb-faqorder-demo">
  <div class="vb-faqorder-wrap" data-vb-faqorder>
    <div class="vb-faqorder-head">
      <span>Example 15</span>
      <h3>FAQ Order Manager</h3>
      <p>Drag FAQ handles to change the order. Click a question to preview the answer.</p>
    </div>

    <div class="vb-faqorder-layout">
      <div class="vb-faqorder-list" data-vb-faqorder-list>
        <article class="vb-faqorder-item is-open" data-vb-faqorder-item data-title="How does drag and drop work?">
          <div class="vb-faqorder-question">
            <button type="button" data-vb-faqorder-handle>⋮⋮</button>
            <strong data-vb-faqorder-number>1</strong>
            <h4 data-vb-faqorder-toggle>How does drag and drop work?</h4>
          </div>
          <p>JavaScript tracks the dragged item, follows the pointer position, and updates the order when the user moves over another item.</p>
        </article>

        <article class="vb-faqorder-item" data-vb-faqorder-item data-title="Can this work on mobile?">
          <div class="vb-faqorder-question">
            <button type="button" data-vb-faqorder-handle>⋮⋮</button>
            <strong data-vb-faqorder-number>2</strong>
            <h4 data-vb-faqorder-toggle>Can this work on mobile?</h4>
          </div>
          <p>Yes. This demo uses both mouse and touch events, so it can work on desktop, tablet, and mobile layouts.</p>
        </article>

        <article class="vb-faqorder-item" data-vb-faqorder-item data-title="Why use FAQ ordering?">
          <div class="vb-faqorder-question">
            <button type="button" data-vb-faqorder-handle>⋮⋮</button>
            <strong data-vb-faqorder-number>3</strong>
            <h4 data-vb-faqorder-toggle>Why use FAQ ordering?</h4>
          </div>
          <p>FAQ ordering helps place the most important customer questions higher on the page for better usability and SEO.</p>
        </article>

        <article class="vb-faqorder-item" data-vb-faqorder-item data-title="Is this useful for admin panels?">
          <div class="vb-faqorder-question">
            <button type="button" data-vb-faqorder-handle>⋮⋮</button>
            <strong data-vb-faqorder-number>4</strong>
            <h4 data-vb-faqorder-toggle>Is this useful for admin panels?</h4>
          </div>
          <p>Yes. Drag and drop ordering is especially useful for admin interfaces where users manage visible frontend content.</p>
        </article>
      </div>

      <aside class="vb-faqorder-side">
        <span>FAQ order</span>
        <strong data-vb-faqorder-output>1. How does drag and drop work?</strong>
        <p>The first FAQ in the list is treated as the highest priority question.</p>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-faqorder-demo,
.vb-faqorder-demo * {
  box-sizing: border-box;
}

.vb-faqorder-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 10% 12%, rgba(251, 191, 36, 0.18), transparent 34%),
    radial-gradient(circle at 92% 20%, rgba(236, 72, 153, 0.16), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #fdf2f8 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-faqorder-wrap {
  max-width: 1120px;
  margin: 0 auto;
}

.vb-faqorder-head {
  max-width: 780px;
  margin-bottom: 22px;
}

.vb-faqorder-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-faqorder-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-faqorder-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-faqorder-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(260px, 0.65fr);
  gap: 16px;
  align-items: stretch;
}

.vb-faqorder-list {
  display: grid;
  gap: 12px;
  padding: 16px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}

.vb-faqorder-item {
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
  overflow: hidden;
}

.vb-faqorder-item.is-dragging {
  opacity: 0.45;
}

.vb-faqorder-item.is-over {
  border-color: rgba(245, 158, 11, 0.80);
  background: #fffbeb;
}

.vb-faqorder-question {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 14px;
}

.vb-faqorder-question button {
  width: 38px;
  height: 44px;
  border: 0;
  border-radius: 14px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
}

.vb-faqorder-question strong {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-faqorder-question h4 {
  margin: 0 !important;
  cursor: pointer;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
}

.vb-faqorder-item p {
  display: none;
  margin: 0 !important;
  padding: 0 16px 16px 88px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-faqorder-item.is-open p {
  display: block;
}

.vb-faqorder-side {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 26px;
  border-radius: 28px;
  background:
    radial-gradient(circle at 16% 16%, rgba(251, 191, 36, 0.22), transparent 36%),
    linear-gradient(135deg, #0f172a, #713f12) !important;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.20);
}

.vb-faqorder-side span {
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-faqorder-side strong {
  margin: 12px 0;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(24px, 4vw, 40px);
  line-height: 1.08;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-faqorder-side p {
  margin: 0 !important;
  color: #fef3c7 !important;
  -webkit-text-fill-color: #fef3c7 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-faqorder-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(340px, calc(100vw - 32px));
  padding: 13px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(245, 158, 11, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
}

.vb-faqorder-ghost strong {
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 14px;
  font-weight: 950;
}

@media (max-width: 860px) {
  .vb-faqorder-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-faqorder-question {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .vb-faqorder-question strong {
    display: none;
  }

  .vb-faqorder-item p {
    padding: 0 14px 14px 64px;
  }
}

This JavaScript FAQ order manager is useful for help centers, product FAQ sections, support portals, documentation pages, ecommerce content blocks, and SEO FAQ management tools.

16. Drag to Delete Interaction

A drag to delete interaction is a practical JavaScript UI pattern where users drag an item into a trash zone to remove it. This is useful for task apps, image galleries, admin lists, file managers, saved items, cart interfaces, and mobile-style dashboards.

Example 16

Drag to Delete Interaction

Drag any task card into the trash zone. The item disappears and can be restored with the undo button.

Old landing page draft

Unused website section

Duplicate image export

Media library cleanup

Outdated FAQ block

Old support content

Unused campaign banner

Marketing asset

Drag an item into the trash zone.

JavaScript

(function () {
  function initTrashDrag() {
    document.querySelectorAll("[data-vb-trashdrag]").forEach(function (root) {
      if (root.getAttribute("data-vb-trashdrag-ready") === "1") return;
      root.setAttribute("data-vb-trashdrag-ready", "1");

      const list = root.querySelector("[data-vb-trashdrag-list]");
      const drop = root.querySelector("[data-vb-trashdrag-drop]");
      const count = root.querySelector("[data-vb-trashdrag-count]");
      const status = root.querySelector("[data-vb-trashdrag-status]");
      const undo = root.querySelector("[data-vb-trashdrag-undo]");

      let active = null;
      let ghost = null;
      let deleted = [];
      let deletedCount = 0;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function isOverTrash(x, y) {
        const el = document.elementFromPoint(x, y);
        return !!(el && el.closest("[data-vb-trashdrag-drop]") === drop);
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-trashdrag-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-trashdrag-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-trashdrag-card]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);
        active = card;
        active.classList.add("is-dragging");
        createGhost(card, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;
        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);

        if (isOverTrash(point.x, point.y)) {
          drop.classList.add("is-over");
        } else {
          drop.classList.remove("is-over");
        }
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);

        if (isOverTrash(point.x, point.y)) {
          deleted.push(active);
          active.remove();
          deletedCount += 1;
          count.textContent = deletedCount;
          undo.disabled = false;
          status.textContent = "Deleted: " + active.getAttribute("data-title");
        }

        cleanup();
      }

      function cancel() {
        cleanup();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        drop.classList.remove("is-over");

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      undo.addEventListener("click", function () {
        const restored = deleted.pop();
        if (!restored) return;

        list.appendChild(restored);
        deletedCount = Math.max(0, deletedCount - 1);
        count.textContent = deletedCount;
        undo.disabled = deleted.length === 0;
        status.textContent = "Restored: " + restored.getAttribute("data-title");
      });

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initTrashDrag);
  } else {
    initTrashDrag();
  }
})();

HTML

<div class="vb-trashdrag-demo">
  <div class="vb-trashdrag-wrap" data-vb-trashdrag>
    <div class="vb-trashdrag-head">
      <span>Example 16</span>
      <h3>Drag to Delete Interaction</h3>
      <p>Drag any task card into the trash zone. The item disappears and can be restored with the undo button.</p>
    </div>

    <div class="vb-trashdrag-layout">
      <section class="vb-trashdrag-list" data-vb-trashdrag-list>
        <article class="vb-trashdrag-card" data-vb-trashdrag-card data-title="Old landing page draft">
          <button type="button" data-vb-trashdrag-handle>⋮⋮</button>
          <div><h4>Old landing page draft</h4><p>Unused website section</p></div>
        </article>

        <article class="vb-trashdrag-card" data-vb-trashdrag-card data-title="Duplicate image export">
          <button type="button" data-vb-trashdrag-handle>⋮⋮</button>
          <div><h4>Duplicate image export</h4><p>Media library cleanup</p></div>
        </article>

        <article class="vb-trashdrag-card" data-vb-trashdrag-card data-title="Outdated FAQ block">
          <button type="button" data-vb-trashdrag-handle>⋮⋮</button>
          <div><h4>Outdated FAQ block</h4><p>Old support content</p></div>
        </article>

        <article class="vb-trashdrag-card" data-vb-trashdrag-card data-title="Unused campaign banner">
          <button type="button" data-vb-trashdrag-handle>⋮⋮</button>
          <div><h4>Unused campaign banner</h4><p>Marketing asset</p></div>
        </article>
      </section>

      <aside class="vb-trashdrag-panel">
        <div class="vb-trashdrag-zone" data-vb-trashdrag-drop>
          <span>Trash Zone</span>
          <strong>Drop here to delete</strong>
          <p>Deleted items: <b data-vb-trashdrag-count>0</b></p>
        </div>

        <button type="button" class="vb-trashdrag-undo" data-vb-trashdrag-undo disabled>Undo last delete</button>
      </aside>
    </div>

    <div class="vb-trashdrag-status" data-vb-trashdrag-status>Drag an item into the trash zone.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript drag to delete interaction is useful for task apps, admin lists, saved item dashboards, file managers, image galleries, shopping carts, and mobile-style UI components.

17. Drag to Favorite / Save Item

A drag to favorite interaction lets users save cards by dragging them into a favorites area. This pattern is useful for product wishlists, article saving, portfolio collections, saved tools, recipe apps, dashboards, and ecommerce interfaces.

Example 17

Drag to Favorite / Save Item

Drag an article card into the saved collection area. Duplicate items are ignored automatically.

Modern CSS Layouts

Grid, Flexbox and layout examples

JavaScript Form Validation

Input checks and checkout forms

CSS Pricing Tables

Responsive pricing section designs

Drag an article into the saved collection.

JavaScript

(function () {
  function initSaveZone() {
    document.querySelectorAll("[data-vb-savezone]").forEach(function (root) {
      if (root.getAttribute("data-vb-savezone-ready") === "1") return;
      root.setAttribute("data-vb-savezone-ready", "1");

      const drop = root.querySelector("[data-vb-savezone-drop]");
      const savedList = root.querySelector("[data-vb-savezone-list]");
      const count = root.querySelector("[data-vb-savezone-count]");
      const status = root.querySelector("[data-vb-savezone-status]");

      let active = null;
      let ghost = null;
      let saved = [];

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function isOverDrop(x, y) {
        const el = document.elementFromPoint(x, y);
        return !!(el && el.closest("[data-vb-savezone-drop]") === drop);
      }

      function renderSaved() {
        savedList.innerHTML = "";

        saved.forEach(function (item) {
          const div = document.createElement("div");
          div.className = "vb-savezone-saved-item";
          div.textContent = "Saved: " + item.title;
          savedList.appendChild(div);
        });

        count.textContent = saved.length === 1 ? "1 saved" : saved.length + " saved";
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-savezone-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-savezone-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-savezone-card]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);
        active = card;
        active.classList.add("is-dragging");
        createGhost(card, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;
        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);

        if (isOverDrop(point.x, point.y)) {
          drop.classList.add("is-over");
        } else {
          drop.classList.remove("is-over");
        }
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);

        if (isOverDrop(point.x, point.y)) {
          const id = active.getAttribute("data-id");
          const title = active.getAttribute("data-title");
          const exists = saved.some(function (item) { return item.id === id; });

          if (!exists) {
            saved.push({ id: id, title: title });
            status.textContent = title + " added to saved items.";
          } else {
            status.textContent = title + " is already saved.";
          }

          renderSaved();
        }

        cleanup();
      }

      function cancel() {
        cleanup();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        drop.classList.remove("is-over");

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      renderSaved();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initSaveZone);
  } else {
    initSaveZone();
  }
})();

HTML

<div class="vb-savezone-demo">
  <div class="vb-savezone-wrap" data-vb-savezone>
    <div class="vb-savezone-head">
      <span>Example 17</span>
      <h3>Drag to Favorite / Save Item</h3>
      <p>Drag an article card into the saved collection area. Duplicate items are ignored automatically.</p>
    </div>

    <div class="vb-savezone-layout">
      <section class="vb-savezone-feed">
        <article class="vb-savezone-card" data-vb-savezone-card data-id="css-layouts" data-title="Modern CSS Layouts">
          <button type="button" data-vb-savezone-handle>⋮⋮</button>
          <div class="vb-savezone-image vb-savezone-blue"></div>
          <div><h4>Modern CSS Layouts</h4><p>Grid, Flexbox and layout examples</p></div>
        </article>

        <article class="vb-savezone-card" data-vb-savezone-card data-id="js-forms" data-title="JavaScript Form Validation">
          <button type="button" data-vb-savezone-handle>⋮⋮</button>
          <div class="vb-savezone-image vb-savezone-green"></div>
          <div><h4>JavaScript Form Validation</h4><p>Input checks and checkout forms</p></div>
        </article>

        <article class="vb-savezone-card" data-vb-savezone-card data-id="pricing-tables" data-title="CSS Pricing Tables">
          <button type="button" data-vb-savezone-handle>⋮⋮</button>
          <div class="vb-savezone-image vb-savezone-orange"></div>
          <div><h4>CSS Pricing Tables</h4><p>Responsive pricing section designs</p></div>
        </article>
      </section>

      <aside class="vb-savezone-panel">
        <div class="vb-savezone-target" data-vb-savezone-drop>
          <span>Saved Collection</span>
          <strong data-vb-savezone-count>0 saved</strong>
          <p>Drop article cards here.</p>
        </div>

        <div class="vb-savezone-saved" data-vb-savezone-list></div>
      </aside>
    </div>

    <div class="vb-savezone-status" data-vb-savezone-status>Drag an article into the saved collection.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript drag to favorite interaction is useful for product wishlists, saved articles, recipe collections, portfolio tools, dashboard cards, and ecommerce save-for-later features.

18. Drag and Drop Product Comparison Builder

A drag and drop product comparison builder lets users drag products into comparison slots. This is useful for ecommerce stores, SaaS pricing tools, product review pages, affiliate websites, marketplace pages, and recommendation interfaces.

Example 18

Product Comparison Builder

Drag products into the comparison area. The comparison table updates automatically.

Starter Plan

€19 · Basic speed · Email support

Pro Plan

€49 · Fast speed · Priority support

Business Plan

€99 · Very fast · Dedicated support

Enterprise Plan

Custom · Maximum speed · Account manager

Comparison area 0 / 3 products

Drop products here.

FeatureDrop products to compare
Drag a product into the comparison area.

JavaScript

(function () {
  function initCompareBuilder() {
    document.querySelectorAll("[data-vb-compare]").forEach(function (root) {
      if (root.getAttribute("data-vb-compare-ready") === "1") return;
      root.setAttribute("data-vb-compare-ready", "1");

      const drop = root.querySelector("[data-vb-compare-drop]");
      const count = root.querySelector("[data-vb-compare-count]");
      const table = root.querySelector("[data-vb-compare-table]");
      const status = root.querySelector("[data-vb-compare-status]");
      let active = null;
      let ghost = null;
      let compared = [];

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function isOverDrop(x, y) {
        const el = document.elementFromPoint(x, y);
        return !!(el && el.closest("[data-vb-compare-drop]") === drop);
      }

      function cardData(card) {
        return {
          id: card.getAttribute("data-id"),
          title: card.getAttribute("data-title"),
          price: card.getAttribute("data-price"),
          speed: card.getAttribute("data-speed"),
          support: card.getAttribute("data-support")
        };
      }

      function renderTable() {
        count.textContent = compared.length + " / 3 products";

        if (!compared.length) {
          table.innerHTML = "<tr><th>Feature</th><td>Drop products to compare</td></tr>";
          return;
        }

        function row(label, key) {
          return "<tr><th>" + label + "</th>" + compared.map(function (item) {
            return "<td>" + item[key] + "</td>";
          }).join("") + "</tr>";
        }

        table.innerHTML =
          row("Product", "title") +
          row("Price", "price") +
          row("Speed", "speed") +
          row("Support", "support");
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-compare-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-compare-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-compare-card]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);
        active = card;
        active.classList.add("is-dragging");
        createGhost(card, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;
        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);

        if (isOverDrop(point.x, point.y)) {
          drop.classList.add("is-over");
        } else {
          drop.classList.remove("is-over");
        }
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);

        if (isOverDrop(point.x, point.y)) {
          const data = cardData(active);
          const exists = compared.some(function (item) { return item.id === data.id; });

          if (exists) {
            status.textContent = data.title + " is already in the comparison table.";
          } else if (compared.length >= 3) {
            status.textContent = "Maximum 3 products can be compared.";
          } else {
            compared.push(data);
            status.textContent = data.title + " added to comparison.";
          }

          renderTable();
        }

        cleanup();
      }

      function cancel() {
        cleanup();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        drop.classList.remove("is-over");

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      renderTable();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initCompareBuilder);
  } else {
    initCompareBuilder();
  }
})();

HTML

<div class="vb-compare-demo">
  <div class="vb-compare-wrap" data-vb-compare>
    <div class="vb-compare-head">
      <span>Example 18</span>
      <h3>Product Comparison Builder</h3>
      <p>Drag products into the comparison area. The comparison table updates automatically.</p>
    </div>

    <div class="vb-compare-layout">
      <section class="vb-compare-products">
        <article class="vb-compare-product" data-vb-compare-card data-id="starter" data-title="Starter Plan" data-price="€19" data-speed="Basic" data-support="Email">
          <button type="button" data-vb-compare-handle>⋮⋮</button>
          <div><h4>Starter Plan</h4><p>€19 · Basic speed · Email support</p></div>
        </article>

        <article class="vb-compare-product" data-vb-compare-card data-id="pro" data-title="Pro Plan" data-price="€49" data-speed="Fast" data-support="Priority">
          <button type="button" data-vb-compare-handle>⋮⋮</button>
          <div><h4>Pro Plan</h4><p>€49 · Fast speed · Priority support</p></div>
        </article>

        <article class="vb-compare-product" data-vb-compare-card data-id="business" data-title="Business Plan" data-price="€99" data-speed="Very Fast" data-support="Dedicated">
          <button type="button" data-vb-compare-handle>⋮⋮</button>
          <div><h4>Business Plan</h4><p>€99 · Very fast · Dedicated support</p></div>
        </article>

        <article class="vb-compare-product" data-vb-compare-card data-id="enterprise" data-title="Enterprise Plan" data-price="Custom" data-speed="Maximum" data-support="Account Manager">
          <button type="button" data-vb-compare-handle>⋮⋮</button>
          <div><h4>Enterprise Plan</h4><p>Custom · Maximum speed · Account manager</p></div>
        </article>
      </section>

      <section class="vb-compare-panel">
        <div class="vb-compare-drop" data-vb-compare-drop>
          <span>Comparison area</span>
          <strong data-vb-compare-count>0 / 3 products</strong>
          <p>Drop products here.</p>
        </div>

        <div class="vb-compare-table-wrap">
          <table class="vb-compare-table">
            <tbody data-vb-compare-table>
              <tr><th>Feature</th><td>Drop products to compare</td></tr>
            </tbody>
          </table>
        </div>
      </section>
    </div>

    <div class="vb-compare-status" data-vb-compare-status>Drag a product into the comparison area.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript product comparison builder is useful for ecommerce stores, SaaS pricing pages, product review posts, affiliate websites, marketplace layouts, and recommendation interfaces.

Need a custom interactive JavaScript feature? We can build drag and drop tools, calculators, forms, dashboards, and custom website components for your business.

Contact us

19. Drag and Drop Pricing Feature Selector

A drag and drop pricing feature selector lets users build a custom plan by dragging features into a pricing box. This is useful for SaaS pricing pages, custom quote builders, hosting plans, service packages, subscription pages, and product configuration tools.

Example 19

Pricing Feature Selector

Drag extra features into the pricing plan. The monthly price updates instantly.

Advanced Analytics

€12 / month

Workflow Automation

€19 / month

Priority Support

€15 / month

White Label Branding

€25 / month

Drag a feature into the custom plan.

JavaScript

(function () {
  function initPricePicker() {
    document.querySelectorAll("[data-vb-pricepick]").forEach(function (root) {
      if (root.getAttribute("data-vb-pricepick-ready") === "1") return;
      root.setAttribute("data-vb-pricepick-ready", "1");

      const drop = root.querySelector("[data-vb-pricepick-drop]");
      const list = root.querySelector("[data-vb-pricepick-list]");
      const total = root.querySelector("[data-vb-pricepick-total]");
      const status = root.querySelector("[data-vb-pricepick-status]");
      const basePrice = 29;

      let active = null;
      let ghost = null;
      let selected = [];

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function isOverDrop(x, y) {
        const el = document.elementFromPoint(x, y);
        return !!(el && el.closest("[data-vb-pricepick-drop]") === drop);
      }

      function cardData(card) {
        return {
          id: card.getAttribute("data-id"),
          title: card.getAttribute("data-title"),
          price: Number(card.getAttribute("data-price"))
        };
      }

      function renderSelected() {
        list.innerHTML = "";

        let price = basePrice;

        selected.forEach(function (item) {
          price += item.price;

          const row = document.createElement("div");
          row.className = "vb-pricepick-selected-item";
          row.innerHTML = "<span>" + item.title + "</span><b>+€" + item.price + "</b>";
          list.appendChild(row);
        });

        total.textContent = "€" + price;
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-pricepick-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-pricepick-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-pricepick-card]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);
        active = card;
        active.classList.add("is-dragging");
        createGhost(card, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;
        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);

        if (isOverDrop(point.x, point.y)) {
          drop.classList.add("is-over");
        } else {
          drop.classList.remove("is-over");
        }
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);

        if (isOverDrop(point.x, point.y)) {
          const data = cardData(active);
          const exists = selected.some(function (item) { return item.id === data.id; });

          if (exists) {
            status.textContent = data.title + " is already included.";
          } else {
            selected.push(data);
            status.textContent = data.title + " added to the plan.";
            renderSelected();
          }
        }

        cleanup();
      }

      function cancel() {
        cleanup();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        drop.classList.remove("is-over");

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      renderSelected();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initPricePicker);
  } else {
    initPricePicker();
  }
})();

HTML

<div class="vb-pricepick-demo">
  <div class="vb-pricepick-wrap" data-vb-pricepick>
    <div class="vb-pricepick-head">
      <span>Example 19</span>
      <h3>Pricing Feature Selector</h3>
      <p>Drag extra features into the pricing plan. The monthly price updates instantly.</p>
    </div>

    <div class="vb-pricepick-layout">
      <section class="vb-pricepick-features">
        <article class="vb-pricepick-feature" data-vb-pricepick-card data-id="analytics" data-title="Advanced Analytics" data-price="12">
          <button type="button" data-vb-pricepick-handle>⋮⋮</button>
          <div><h4>Advanced Analytics</h4><p>€12 / month</p></div>
        </article>

        <article class="vb-pricepick-feature" data-vb-pricepick-card data-id="automation" data-title="Workflow Automation" data-price="19">
          <button type="button" data-vb-pricepick-handle>⋮⋮</button>
          <div><h4>Workflow Automation</h4><p>€19 / month</p></div>
        </article>

        <article class="vb-pricepick-feature" data-vb-pricepick-card data-id="support" data-title="Priority Support" data-price="15">
          <button type="button" data-vb-pricepick-handle>⋮⋮</button>
          <div><h4>Priority Support</h4><p>€15 / month</p></div>
        </article>

        <article class="vb-pricepick-feature" data-vb-pricepick-card data-id="branding" data-title="White Label Branding" data-price="25">
          <button type="button" data-vb-pricepick-handle>⋮⋮</button>
          <div><h4>White Label Branding</h4><p>€25 / month</p></div>
        </article>
      </section>

      <aside class="vb-pricepick-plan">
        <div class="vb-pricepick-cardbox" data-vb-pricepick-drop>
          <span>Custom Plan</span>
          <strong data-vb-pricepick-total>€29</strong>
          <p>Base plan + selected features</p>
          <div class="vb-pricepick-selected" data-vb-pricepick-list></div>
        </div>
      </aside>
    </div>

    <div class="vb-pricepick-status" data-vb-pricepick-status>Drag a feature into the custom plan.</div>
  </div>
</div>

CSS

.vb-pricepick-demo,
.vb-pricepick-demo * {
  box-sizing: border-box;
}

.vb-pricepick-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(34, 197, 94, 0.16), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #eff6ff 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-pricepick-wrap {
  max-width: 1120px;
  margin: 0 auto;
}

.vb-pricepick-head {
  max-width: 780px;
  margin-bottom: 22px;
}

.vb-pricepick-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-pricepick-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-pricepick-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-pricepick-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 0.7fr);
  gap: 16px;
}

.vb-pricepick-features {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  align-content: start;
}

.vb-pricepick-feature {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 16px;
  border-radius: 24px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 16px 44px rgba(15, 23, 42, 0.08);
}

.vb-pricepick-feature.is-dragging {
  opacity: 0.45;
}

.vb-pricepick-feature button {
  width: 38px;
  height: 44px;
  border: 0;
  border-radius: 14px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
}

.vb-pricepick-feature h4 {
  margin: 0 0 5px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
}

.vb-pricepick-feature p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.45;
  font-weight: 700;
}

.vb-pricepick-plan {
  min-width: 0;
}

.vb-pricepick-cardbox {
  min-height: 360px;
  padding: 26px;
  border-radius: 32px;
  background:
    radial-gradient(circle at 16% 14%, rgba(34, 197, 94, 0.26), transparent 38%),
    linear-gradient(135deg, #052e16, #0f172a) !important;
  border: 1px dashed rgba(134, 239, 172, 0.75);
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.22);
}

.vb-pricepick-cardbox.is-over {
  box-shadow: inset 0 0 0 5px rgba(34, 197, 94, 0.18), 0 22px 64px rgba(15, 23, 42, 0.22);
}

.vb-pricepick-cardbox > span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-pricepick-cardbox > strong {
  display: block;
  margin: 12px 0 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(44px, 6vw, 70px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.07em;
}

.vb-pricepick-cardbox > p {
  margin: 0 0 16px !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 15px;
  line-height: 1.55;
  font-weight: 700;
}

.vb-pricepick-selected {
  display: grid;
  gap: 10px;
}

.vb-pricepick-selected-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px;
  border-radius: 16px;
  background: rgba(255,255,255,0.12);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-pricepick-selected-item b {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
}

.vb-pricepick-status {
  margin-top: 16px;
  padding: 13px 15px;
  border-radius: 18px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-pricepick-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(320px, calc(100vw - 32px));
  padding: 13px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(34, 197, 94, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
}

.vb-pricepick-ghost strong {
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 14px;
  font-weight: 950;
}

@media (max-width: 860px) {
  .vb-pricepick-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-pricepick-features {
    grid-template-columns: 1fr;
  }
}

This JavaScript pricing feature selector is useful for SaaS pricing pages, service package builders, hosting plan configurators, quote tools, and subscription product pages.

20. Drag and Drop Quiz Matching Game

A drag and drop quiz matching game is an interactive JavaScript learning pattern where users match answers to the correct question cards. It is useful for education websites, onboarding flows, language learning apps, product training, quizzes, and gamified learning interfaces.

Example 20

Quiz Matching Game

Drag each answer into the matching question card. Correct matches stay locked.

Score 0 / 3 correct

What language structures a web page?

Drop answer here

What language styles a web page?

Drop answer here

What language adds interaction?

Drop answer here
CSS
JavaScript
HTML
Drag an answer into a question card.

JavaScript

(function () {
  function initQuizMatch() {
    document.querySelectorAll("[data-vb-quizmatch]").forEach(function (root) {
      if (root.getAttribute("data-vb-quizmatch-ready") === "1") return;
      root.setAttribute("data-vb-quizmatch-ready", "1");

      const drops = Array.from(root.querySelectorAll("[data-vb-quizmatch-drop]"));
      const score = root.querySelector("[data-vb-quizmatch-score]");
      const status = root.querySelector("[data-vb-quizmatch-status]");
      let active = null;
      let ghost = null;
      let correct = 0;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function getDropAt(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el) return null;
        const drop = el.closest("[data-vb-quizmatch-drop]");
        return drops.includes(drop) ? drop : null;
      }

      function clearOver() {
        drops.forEach(function (drop) {
          drop.classList.remove("is-over", "is-wrong");
        });
      }

      function updateScore() {
        score.textContent = correct + " / 3 correct";
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-quizmatch-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-label") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-quizmatch-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-quizmatch-answer]");
        if (!card || card.classList.contains("is-locked")) return;

        event.preventDefault();

        const point = getPoint(event);
        active = card;
        active.classList.add("is-dragging");
        createGhost(card, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;
        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        clearOver();

        const drop = getDropAt(point.x, point.y);
        if (drop && !drop.classList.contains("is-correct")) {
          drop.classList.add("is-over");
        }
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);
        const drop = getDropAt(point.x, point.y);

        if (drop && !drop.classList.contains("is-correct")) {
          const answer = active.getAttribute("data-id");
          const correctAnswer = drop.getAttribute("data-answer");

          if (answer === correctAnswer) {
            const slot = drop.querySelector("[data-vb-quizmatch-slot]");
            slot.textContent = active.getAttribute("data-label");
            drop.classList.add("is-correct");
            active.classList.add("is-locked");
            correct += 1;
            status.textContent = "Correct match: " + active.getAttribute("data-label");
            updateScore();
          } else {
            drop.classList.add("is-wrong");
            status.textContent = "Wrong answer. Try another question.";
          }
        }

        cleanup();
      }

      function cancel() {
        cleanup();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        drops.forEach(function (drop) { drop.classList.remove("is-over"); });

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateScore();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initQuizMatch);
  } else {
    initQuizMatch();
  }
})();

HTML

<div class="vb-quizmatch-demo">
  <div class="vb-quizmatch-wrap" data-vb-quizmatch>
    <div class="vb-quizmatch-head">
      <span>Example 20</span>
      <h3>Quiz Matching Game</h3>
      <p>Drag each answer into the matching question card. Correct matches stay locked.</p>
    </div>

    <div class="vb-quizmatch-score">
      <span>Score</span>
      <strong data-vb-quizmatch-score>0 / 3 correct</strong>
    </div>

    <div class="vb-quizmatch-layout">
      <section class="vb-quizmatch-questions">
        <article class="vb-quizmatch-question" data-vb-quizmatch-drop data-answer="html">
          <h4>What language structures a web page?</h4>
          <div class="vb-quizmatch-slot" data-vb-quizmatch-slot>Drop answer here</div>
        </article>

        <article class="vb-quizmatch-question" data-vb-quizmatch-drop data-answer="css">
          <h4>What language styles a web page?</h4>
          <div class="vb-quizmatch-slot" data-vb-quizmatch-slot>Drop answer here</div>
        </article>

        <article class="vb-quizmatch-question" data-vb-quizmatch-drop data-answer="javascript">
          <h4>What language adds interaction?</h4>
          <div class="vb-quizmatch-slot" data-vb-quizmatch-slot>Drop answer here</div>
        </article>
      </section>

      <section class="vb-quizmatch-answers" data-vb-quizmatch-pool>
        <article class="vb-quizmatch-answer" data-vb-quizmatch-answer data-id="css" data-label="CSS">
          <button type="button" data-vb-quizmatch-handle>⋮⋮</button>
          <strong>CSS</strong>
        </article>

        <article class="vb-quizmatch-answer" data-vb-quizmatch-answer data-id="javascript" data-label="JavaScript">
          <button type="button" data-vb-quizmatch-handle>⋮⋮</button>
          <strong>JavaScript</strong>
        </article>

        <article class="vb-quizmatch-answer" data-vb-quizmatch-answer data-id="html" data-label="HTML">
          <button type="button" data-vb-quizmatch-handle>⋮⋮</button>
          <strong>HTML</strong>
        </article>
      </section>
    </div>

    <div class="vb-quizmatch-status" data-vb-quizmatch-status>Drag an answer into a question card.</div>
  </div>
</div>

CSS

.vb-quizmatch-demo,
.vb-quizmatch-demo * {
  box-sizing: border-box;
}

.vb-quizmatch-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(168, 85, 247, 0.16), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(249, 115, 22, 0.16), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #fff7ed 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-quizmatch-wrap {
  max-width: 1120px;
  margin: 0 auto;
}

.vb-quizmatch-head {
  max-width: 780px;
  margin-bottom: 18px;
}

.vb-quizmatch-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #f3e8ff;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-quizmatch-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-quizmatch-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-quizmatch-score {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: center;
  margin-bottom: 16px;
  padding: 14px 16px;
  border-radius: 20px;
  background: #0f172a;
}

.vb-quizmatch-score span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-quizmatch-score strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-quizmatch-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 0.45fr);
  gap: 16px;
}

.vb-quizmatch-questions {
  display: grid;
  gap: 14px;
}

.vb-quizmatch-question {
  padding: 18px;
  border-radius: 26px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 16px 44px rgba(15, 23, 42, 0.08);
}

.vb-quizmatch-question.is-over {
  border-color: rgba(168, 85, 247, 0.84);
  background: #faf5ff;
  box-shadow: inset 0 0 0 4px rgba(168, 85, 247, 0.10), 0 16px 44px rgba(15, 23, 42, 0.08);
}

.vb-quizmatch-question.is-correct {
  border-color: rgba(34, 197, 94, 0.75);
  background: #f0fdf4;
}

.vb-quizmatch-question.is-wrong {
  border-color: rgba(239, 68, 68, 0.72);
  background: #fef2f2;
}

.vb-quizmatch-question h4 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
}

.vb-quizmatch-slot {
  min-height: 52px;
  display: flex;
  align-items: center;
  padding: 12px;
  border-radius: 16px;
  background: #f8fafc;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 800;
}

.vb-quizmatch-answers {
  display: grid;
  align-content: start;
  gap: 12px;
  padding: 16px;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}

.vb-quizmatch-answer {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 14px;
  border-radius: 18px;
  background: #faf5ff;
  border: 1px solid rgba(168, 85, 247, 0.20);
}

.vb-quizmatch-answer.is-dragging {
  opacity: 0.45;
}

.vb-quizmatch-answer.is-locked {
  opacity: 0.35;
  pointer-events: none;
}

.vb-quizmatch-answer button {
  width: 36px;
  height: 42px;
  border: 0;
  border-radius: 14px;
  background: #7e22ce;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 19px;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
}

.vb-quizmatch-answer strong {
  color: #581c87 !important;
  -webkit-text-fill-color: #581c87 !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-quizmatch-status {
  margin-top: 16px;
  padding: 13px 15px;
  border-radius: 18px;
  background: #581c87;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-quizmatch-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(260px, calc(100vw - 32px));
  padding: 13px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(168, 85, 247, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.24);
}

.vb-quizmatch-ghost strong {
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 14px;
  font-weight: 950;
}

@media (max-width: 820px) {
  .vb-quizmatch-layout {
    grid-template-columns: 1fr;
  }
}

This JavaScript drag and drop quiz matching game is useful for education websites, onboarding screens, product training, quizzes, language learning apps, and interactive learning interfaces.

21. Drag and Drop Puzzle Grid

A drag and drop puzzle grid turns sorting into a small interactive game. Users drag tiles into the correct order and the interface checks whether the puzzle is solved. This pattern is useful for educational games, onboarding tasks, visual learning tools, gamified UI demos, and interactive tutorials.

Example 21

Drag and Drop Puzzle Grid

Reorder the tiles from 1 to 9. The status updates when the puzzle is solved.

JavaScript

(function () {
  function initPuzzleGrid() {
    document.querySelectorAll("[data-vb-puzzlegrid]").forEach(function (root) {
      if (root.getAttribute("data-vb-puzzlegrid-ready") === "1") return;
      root.setAttribute("data-vb-puzzlegrid-ready", "1");

      const board = root.querySelector("[data-vb-puzzlegrid-board]");
      const status = root.querySelector("[data-vb-puzzlegrid-status]");
      const shuffle = root.querySelector("[data-vb-puzzlegrid-shuffle]");
      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function orderValues() {
        return Array.from(board.querySelectorAll("[data-vb-puzzlegrid-tile]")).map(function (tile) {
          return tile.getAttribute("data-value");
        });
      }

      function checkSolved() {
        const solved = orderValues().join("") === "123456789";
        status.textContent = solved ? "Puzzle solved!" : "Not solved yet";
      }

      function createGhost(tile, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-puzzlegrid-ghost";
        ghost.textContent = tile.textContent;
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function clearOver() {
        board.querySelectorAll(".is-over").forEach(function (tile) {
          tile.classList.remove("is-over");
        });
      }

      function reorderAt(x, y) {
        const el = document.elementFromPoint(x, y);
        const target = el ? el.closest("[data-vb-puzzlegrid-tile]") : null;

        if (!target || target === active || !board.contains(target)) return;

        clearOver();
        target.classList.add("is-over");

        const allTiles = Array.from(board.querySelectorAll("[data-vb-puzzlegrid-tile]"));
        const activeIndex = allTiles.indexOf(active);
        const targetIndex = allTiles.indexOf(target);

        if (activeIndex < targetIndex) {
          target.insertAdjacentElement("afterend", active);
        } else {
          target.insertAdjacentElement("beforebegin", active);
        }

        checkSolved();
      }

      function start(event) {
        const tile = event.target.closest("[data-vb-puzzlegrid-tile]");
        if (!tile) return;

        event.preventDefault();

        const point = getPoint(event);
        active = tile;
        active.classList.add("is-dragging");
        createGhost(tile, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        reorderAt(point.x, point.y);
      }

      function end() {
        cleanup();
        checkSolved();
      }

      function cancel() {
        cleanup();
        checkSolved();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      shuffle.addEventListener("click", function () {
        const tiles = Array.from(board.querySelectorAll("[data-vb-puzzlegrid-tile]"));
        tiles.sort(function () {
          return Math.random() - 0.5;
        });
        tiles.forEach(function (tile) {
          board.appendChild(tile);
        });
        checkSolved();
      });

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      checkSolved();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initPuzzleGrid);
  } else {
    initPuzzleGrid();
  }
})();

HTML

<div class="vb-puzzlegrid-demo">
  <div class="vb-puzzlegrid-wrap" data-vb-puzzlegrid>
    <div class="vb-puzzlegrid-head">
      <span>Example 21</span>
      <h3>Drag and Drop Puzzle Grid</h3>
      <p>Reorder the tiles from 1 to 9. The status updates when the puzzle is solved.</p>
    </div>

    <div class="vb-puzzlegrid-panel">
      <div class="vb-puzzlegrid-board" data-vb-puzzlegrid-board>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="5">5</button>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="1">1</button>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="8">8</button>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="3">3</button>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="7">7</button>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="2">2</button>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="9">9</button>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="4">4</button>
        <button type="button" class="vb-puzzlegrid-tile" data-vb-puzzlegrid-tile data-value="6">6</button>
      </div>

      <aside class="vb-puzzlegrid-side">
        <span>Puzzle Status</span>
        <strong data-vb-puzzlegrid-status>Not solved yet</strong>
        <p>Drag tiles until the order is 1, 2, 3, 4, 5, 6, 7, 8, 9.</p>
        <button type="button" data-vb-puzzlegrid-shuffle>Shuffle tiles</button>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-puzzlegrid-demo,
.vb-puzzlegrid-demo * {
  box-sizing: border-box;
}

.vb-puzzlegrid-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(14, 165, 233, 0.16), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(245, 158, 11, 0.18), transparent 34%),
    linear-gradient(135deg, #ecfeff 0%, #fffbeb 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-puzzlegrid-wrap {
  max-width: 1060px;
  margin: 0 auto;
}

.vb-puzzlegrid-head {
  max-width: 780px;
  margin-bottom: 22px;
}

.vb-puzzlegrid-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #cffafe;
  color: #0e7490 !important;
  -webkit-text-fill-color: #0e7490 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-puzzlegrid-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-puzzlegrid-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-puzzlegrid-panel {
  display: grid;
  grid-template-columns: minmax(280px, 0.85fr) minmax(260px, 0.65fr);
  gap: 16px;
  align-items: stretch;
}

.vb-puzzlegrid-board {
  display: grid;
  grid-template-columns: repeat(3, minmax(72px, 1fr));
  gap: 12px;
  padding: 18px;
  border-radius: 30px;
  background: #0f172a;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.22);
}

.vb-puzzlegrid-tile {
  min-height: 112px;
  border: 0;
  border-radius: 22px;
  background:
    radial-gradient(circle at 25% 18%, rgba(255,255,255,0.38), transparent 32%),
    linear-gradient(135deg, #06b6d4, #2563eb) !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 58px);
  line-height: 1;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.28), 0 12px 26px rgba(0,0,0,0.20);
}

.vb-puzzlegrid-tile:nth-child(2n) {
  background:
    radial-gradient(circle at 25% 18%, rgba(255,255,255,0.38), transparent 32%),
    linear-gradient(135deg, #f59e0b, #ec4899) !important;
}

.vb-puzzlegrid-tile.is-dragging {
  opacity: 0.45;
}

.vb-puzzlegrid-tile.is-over {
  outline: 4px solid rgba(255,255,255,0.38);
}

.vb-puzzlegrid-side {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 26px;
  border-radius: 30px;
  background:
    radial-gradient(circle at 16% 16%, rgba(34, 211, 238, 0.22), transparent 36%),
    linear-gradient(135deg, #164e63, #0f172a) !important;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.20);
}

.vb-puzzlegrid-side span {
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-puzzlegrid-side strong {
  margin: 12px 0;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(26px, 4vw, 42px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-puzzlegrid-side p {
  margin: 0 0 18px !important;
  color: #cffafe !important;
  -webkit-text-fill-color: #cffafe !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-puzzlegrid-side button {
  min-height: 46px;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
}

.vb-puzzlegrid-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: 92px;
  height: 92px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 22px;
  background: linear-gradient(135deg, #06b6d4, #2563eb) !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 42px;
  font-weight: 950;
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.30);
}

@media (max-width: 820px) {
  .vb-puzzlegrid-panel {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 480px) {
  .vb-puzzlegrid-board {
    gap: 8px;
    padding: 12px;
  }

  .vb-puzzlegrid-tile {
    min-height: 82px;
    border-radius: 18px;
  }
}

This JavaScript drag and drop puzzle grid is useful for educational games, onboarding tasks, visual learning tools, gamified UI demos, interactive tutorials, and puzzle-based web components.

22. Drag and Drop Seat Booking Layout

A drag and drop seat booking layout lets users drag people into available seats. This pattern is useful for event booking pages, meeting room planners, classroom layouts, cinema booking prototypes, restaurant seating tools, and conference dashboards.

Example 22

Seat Booking Layout

Drag each guest into an empty seat. The seat keeps the assigned name and updates the counter.

Guests

AN Anna
MK Mark
LS Lisa
TM Tom
Stage
Assigned seats 0 / 6
Drag a guest into an empty seat.

JavaScript

(function () {
  function initSeatBook() {
    document.querySelectorAll("[data-vb-seatbook]").forEach(function (root) {
      if (root.getAttribute("data-vb-seatbook-ready") === "1") return;
      root.setAttribute("data-vb-seatbook-ready", "1");

      const seats = Array.from(root.querySelectorAll("[data-vb-seatbook-seat]"));
      const count = root.querySelector("[data-vb-seatbook-count]");
      const status = root.querySelector("[data-vb-seatbook-status]");

      let active = null;
      let ghost = null;
      let assigned = {};

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function getSeatAt(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el) return null;
        const seat = el.closest("[data-vb-seatbook-seat]");
        return seats.includes(seat) ? seat : null;
      }

      function updateCount() {
        count.textContent = Object.keys(assigned).length + " / " + seats.length;
      }

      function clearOver() {
        seats.forEach(function (seat) {
          seat.classList.remove("is-over");
        });
      }

      function createGhost(guest, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-seatbook-ghost";
        ghost.innerHTML = "<strong>" + guest.getAttribute("data-name") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-seatbook-handle]");
        if (!handle) return;

        const guest = handle.closest("[data-vb-seatbook-guest]");
        if (!guest) return;

        event.preventDefault();

        const point = getPoint(event);
        active = guest;
        active.classList.add("is-dragging");
        createGhost(guest, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;
        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        clearOver();

        const seat = getSeatAt(point.x, point.y);
        if (seat && !seat.classList.contains("is-filled")) {
          seat.classList.add("is-over");
        }
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);
        const seat = getSeatAt(point.x, point.y);

        if (seat && !seat.classList.contains("is-filled")) {
          const guestId = active.getAttribute("data-id");
          const guestName = active.getAttribute("data-name");

          if (assigned[guestId]) {
            status.textContent = guestName + " already has a seat.";
          } else {
            assigned[guestId] = seat.getAttribute("data-seat");
            seat.classList.add("is-filled");
            seat.querySelector("strong").textContent = guestName;
            active.style.display = "none";
            status.textContent = guestName + " assigned to seat " + seat.getAttribute("data-seat") + ".";
            updateCount();
          }
        }

        cleanup();
      }

      function cancel() {
        cleanup();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateCount();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initSeatBook);
  } else {
    initSeatBook();
  }
})();

HTML

<div class="vb-seatbook-demo">
  <div class="vb-seatbook-wrap" data-vb-seatbook>
    <div class="vb-seatbook-head">
      <span>Example 22</span>
      <h3>Seat Booking Layout</h3>
      <p>Drag each guest into an empty seat. The seat keeps the assigned name and updates the counter.</p>
    </div>

    <div class="vb-seatbook-layout">
      <section class="vb-seatbook-guests">
        <h4>Guests</h4>

        <article class="vb-seatbook-guest" data-vb-seatbook-guest data-id="anna" data-name="Anna">
          <button type="button" data-vb-seatbook-handle>⋮⋮</button>
          <span>AN</span>
          <strong>Anna</strong>
        </article>

        <article class="vb-seatbook-guest" data-vb-seatbook-guest data-id="mark" data-name="Mark">
          <button type="button" data-vb-seatbook-handle>⋮⋮</button>
          <span>MK</span>
          <strong>Mark</strong>
        </article>

        <article class="vb-seatbook-guest" data-vb-seatbook-guest data-id="lisa" data-name="Lisa">
          <button type="button" data-vb-seatbook-handle>⋮⋮</button>
          <span>LS</span>
          <strong>Lisa</strong>
        </article>

        <article class="vb-seatbook-guest" data-vb-seatbook-guest data-id="tom" data-name="Tom">
          <button type="button" data-vb-seatbook-handle>⋮⋮</button>
          <span>TM</span>
          <strong>Tom</strong>
        </article>
      </section>

      <section class="vb-seatbook-map">
        <div class="vb-seatbook-stage">Stage</div>

        <div class="vb-seatbook-seats">
          <button type="button" class="vb-seatbook-seat" data-vb-seatbook-seat data-seat="A1"><span>A1</span><strong>Empty</strong></button>
          <button type="button" class="vb-seatbook-seat" data-vb-seatbook-seat data-seat="A2"><span>A2</span><strong>Empty</strong></button>
          <button type="button" class="vb-seatbook-seat" data-vb-seatbook-seat data-seat="A3"><span>A3</span><strong>Empty</strong></button>
          <button type="button" class="vb-seatbook-seat" data-vb-seatbook-seat data-seat="B1"><span>B1</span><strong>Empty</strong></button>
          <button type="button" class="vb-seatbook-seat" data-vb-seatbook-seat data-seat="B2"><span>B2</span><strong>Empty</strong></button>
          <button type="button" class="vb-seatbook-seat" data-vb-seatbook-seat data-seat="B3"><span>B3</span><strong>Empty</strong></button>
        </div>

        <div class="vb-seatbook-summary">
          <span>Assigned seats</span>
          <strong data-vb-seatbook-count>0 / 6</strong>
        </div>
      </section>
    </div>

    <div class="vb-seatbook-status" data-vb-seatbook-status>Drag a guest into an empty seat.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript drag and drop seat booking layout is useful for event seating, classroom tools, restaurant layouts, cinema booking demos, meeting room planners, and conference dashboards.

23. Drag and Drop Timeline Builder

A drag and drop timeline builder lets users reorder project milestones visually. This pattern is useful for project roadmaps, product launch timelines, event planning, content calendars, onboarding steps, and workflow builders.

Example 23

Timeline Builder

Drag milestones up or down to change the project timeline order.

1

Research

Collect requirements and define project scope.

2

Wireframes

Create low-fidelity layouts and user flows.

3

Development

Build the frontend, backend, and integrations.

4

Launch

Publish the final website or product release.

JavaScript

(function () {
  function initTimelineBuilder() {
    document.querySelectorAll("[data-vb-timelinebuild]").forEach(function (root) {
      if (root.getAttribute("data-vb-timelinebuild-ready") === "1") return;
      root.setAttribute("data-vb-timelinebuild-ready", "1");

      const list = root.querySelector("[data-vb-timelinebuild-list]");
      const next = root.querySelector("[data-vb-timelinebuild-next]");
      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateTimeline() {
        const items = Array.from(list.querySelectorAll("[data-vb-timelinebuild-item]"));

        items.forEach(function (item, index) {
          const number = item.querySelector("[data-vb-timelinebuild-number]");
          if (number) number.textContent = index + 1;
        });

        if (items[0]) {
          next.textContent = items[0].getAttribute("data-title");
        }
      }

      function createGhost(item, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-timelinebuild-ghost";
        ghost.innerHTML = "<strong>" + item.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function clearOver() {
        list.querySelectorAll(".is-over").forEach(function (item) {
          item.classList.remove("is-over");
        });
      }

      function reorderAt(x, y) {
        const el = document.elementFromPoint(x, y);
        const target = el ? el.closest("[data-vb-timelinebuild-item]") : null;

        if (!target || target === active || !list.contains(target)) return;

        const rect = target.getBoundingClientRect();
        const after = y > rect.top + rect.height / 2;

        clearOver();
        target.classList.add("is-over");

        if (after) {
          target.insertAdjacentElement("afterend", active);
        } else {
          target.insertAdjacentElement("beforebegin", active);
        }

        updateTimeline();
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-timelinebuild-handle]");
        if (!handle) return;

        const item = handle.closest("[data-vb-timelinebuild-item]");
        if (!item) return;

        event.preventDefault();

        const point = getPoint(event);
        active = item;
        active.classList.add("is-dragging");
        createGhost(item, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        reorderAt(point.x, point.y);
      }

      function end() {
        cleanup();
        updateTimeline();
      }

      function cancel() {
        cleanup();
        updateTimeline();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateTimeline();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initTimelineBuilder);
  } else {
    initTimelineBuilder();
  }
})();

HTML

<div class="vb-timelinebuild-demo">
  <div class="vb-timelinebuild-wrap" data-vb-timelinebuild>
    <div class="vb-timelinebuild-head">
      <span>Example 23</span>
      <h3>Timeline Builder</h3>
      <p>Drag milestones up or down to change the project timeline order.</p>
    </div>

    <div class="vb-timelinebuild-layout">
      <section class="vb-timelinebuild-list" data-vb-timelinebuild-list>
        <article class="vb-timelinebuild-item" data-vb-timelinebuild-item data-title="Research">
          <button type="button" data-vb-timelinebuild-handle>⋮⋮</button>
          <span data-vb-timelinebuild-number>1</span>
          <div><h4>Research</h4><p>Collect requirements and define project scope.</p></div>
        </article>

        <article class="vb-timelinebuild-item" data-vb-timelinebuild-item data-title="Wireframes">
          <button type="button" data-vb-timelinebuild-handle>⋮⋮</button>
          <span data-vb-timelinebuild-number>2</span>
          <div><h4>Wireframes</h4><p>Create low-fidelity layouts and user flows.</p></div>
        </article>

        <article class="vb-timelinebuild-item" data-vb-timelinebuild-item data-title="Development">
          <button type="button" data-vb-timelinebuild-handle>⋮⋮</button>
          <span data-vb-timelinebuild-number>3</span>
          <div><h4>Development</h4><p>Build the frontend, backend, and integrations.</p></div>
        </article>

        <article class="vb-timelinebuild-item" data-vb-timelinebuild-item data-title="Launch">
          <button type="button" data-vb-timelinebuild-handle>⋮⋮</button>
          <span data-vb-timelinebuild-number>4</span>
          <div><h4>Launch</h4><p>Publish the final website or product release.</p></div>
        </article>
      </section>

      <aside class="vb-timelinebuild-side">
        <span>Next milestone</span>
        <strong data-vb-timelinebuild-next>Research</strong>
        <p>The first milestone is treated as the next active step.</p>
      </aside>
    </div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript drag and drop timeline builder is useful for project roadmaps, launch timelines, content calendars, onboarding flows, event schedules, and workflow planning tools.

24. Drag and Drop Tag Organizer

A drag and drop tag organizer lets users sort tags into different groups. This pattern is useful for blog admin panels, CRM filters, product attributes, content categorization tools, preference builders, and dashboard filtering interfaces.

Example 24

Tag Organizer

Drag tags into topic groups. This is a compact pattern for filters, labels, categories, and admin tools.

Available Tags

0

Development

0

Marketing

0
Drag tags into groups.

JavaScript

(function () {
  function initTagOrganizer() {
    document.querySelectorAll("[data-vb-tagorg]").forEach(function (root) {
      if (root.getAttribute("data-vb-tagorg-ready") === "1") return;
      root.setAttribute("data-vb-tagorg-ready", "1");

      const drops = Array.from(root.querySelectorAll("[data-vb-tagorg-drop]"));
      const status = root.querySelector("[data-vb-tagorg-status]");

      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateCounts() {
        drops.forEach(function (drop) {
          const zone = drop.getAttribute("data-zone");
          const count = root.querySelector('[data-vb-tagorg-count="' + zone + '"]');
          if (count) count.textContent = drop.querySelectorAll("[data-vb-tagorg-tag]").length;
        });
      }

      function getDropAt(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el) return null;
        const drop = el.closest("[data-vb-tagorg-drop]");
        return drops.includes(drop) ? drop : null;
      }

      function clearOver() {
        drops.forEach(function (drop) {
          drop.classList.remove("is-over");
        });
      }

      function createGhost(tag, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-tagorg-ghost";
        ghost.textContent = tag.getAttribute("data-label");
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 14 + "px";
        ghost.style.top = y + 14 + "px";
      }

      function start(event) {
        const tag = event.target.closest("[data-vb-tagorg-tag]");
        if (!tag) return;

        event.preventDefault();

        const point = getPoint(event);
        active = tag;
        active.classList.add("is-dragging");
        createGhost(tag, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        clearOver();

        const drop = getDropAt(point.x, point.y);
        if (drop) drop.classList.add("is-over");
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);
        const drop = getDropAt(point.x, point.y);

        if (drop) {
          drop.appendChild(active);
          status.textContent = active.getAttribute("data-label") + " moved to " + drop.getAttribute("data-zone") + ".";
        }

        cleanup();
        updateCounts();
      }

      function cancel() {
        cleanup();
        updateCounts();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateCounts();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initTagOrganizer);
  } else {
    initTagOrganizer();
  }
})();

HTML

<div class="vb-tagorg-demo">
  <div class="vb-tagorg-wrap" data-vb-tagorg>
    <div class="vb-tagorg-head">
      <span>Example 24</span>
      <h3>Tag Organizer</h3>
      <p>Drag tags into topic groups. This is a compact pattern for filters, labels, categories, and admin tools.</p>
    </div>

    <div class="vb-tagorg-layout">
      <section class="vb-tagorg-pool">
        <div class="vb-tagorg-title">
          <h4>Available Tags</h4>
          <strong data-vb-tagorg-count="pool">0</strong>
        </div>

        <div class="vb-tagorg-drop" data-vb-tagorg-drop data-zone="pool">
          <button type="button" class="vb-tagorg-tag" data-vb-tagorg-tag data-label="JavaScript">JavaScript</button>
          <button type="button" class="vb-tagorg-tag" data-vb-tagorg-tag data-label="CSS">CSS</button>
          <button type="button" class="vb-tagorg-tag" data-vb-tagorg-tag data-label="SEO">SEO</button>
          <button type="button" class="vb-tagorg-tag" data-vb-tagorg-tag data-label="WordPress">WordPress</button>
          <button type="button" class="vb-tagorg-tag" data-vb-tagorg-tag data-label="Forms">Forms</button>
          <button type="button" class="vb-tagorg-tag" data-vb-tagorg-tag data-label="Ecommerce">Ecommerce</button>
        </div>
      </section>

      <section class="vb-tagorg-groups">
        <div class="vb-tagorg-group">
          <div class="vb-tagorg-title">
            <h4>Development</h4>
            <strong data-vb-tagorg-count="development">0</strong>
          </div>
          <div class="vb-tagorg-drop" data-vb-tagorg-drop data-zone="development"></div>
        </div>

        <div class="vb-tagorg-group">
          <div class="vb-tagorg-title">
            <h4>Marketing</h4>
            <strong data-vb-tagorg-count="marketing">0</strong>
          </div>
          <div class="vb-tagorg-drop" data-vb-tagorg-drop data-zone="marketing"></div>
        </div>
      </section>
    </div>

    <div class="vb-tagorg-status" data-vb-tagorg-status>Drag tags into groups.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript drag and drop tag organizer is useful for blog admin panels, CRM filters, product attributes, content categorization tools, preference builders, and dashboard filtering interfaces.

Need a custom interactive JavaScript feature? We can build drag and drop tools, calculators, forms, dashboards, and custom website components for your business.

Contact us

25. Drag and Drop Upload with Progress Simulation

A drag and drop upload with progress simulation is useful for file upload interfaces, admin dashboards, media libraries, document portals, profile forms, project tools, and SaaS apps. This demo does not upload files to a real server. It simulates upload progress in the browser so the UI pattern is safe to test inside a blog post.

Example 25

Upload with Progress Simulation

Drop files into the upload zone or click the button. Progress is simulated in the browser.

Drop files here

Images, PDFs, documents or project files

Upload Queue

Files

0 files

No files added yet.

Drag files into the upload zone.

JavaScript

(function () {
  function initUploadSim() {
    document.querySelectorAll("[data-vb-uploadsim]").forEach(function (root) {
      if (root.getAttribute("data-vb-uploadsim-ready") === "1") return;
      root.setAttribute("data-vb-uploadsim-ready", "1");

      const zone = root.querySelector("[data-vb-uploadsim-zone]");
      const input = root.querySelector("[data-vb-uploadsim-input]");
      const browse = root.querySelector("[data-vb-uploadsim-browse]");
      const list = root.querySelector("[data-vb-uploadsim-list]");
      const empty = root.querySelector("[data-vb-uploadsim-empty]");
      const count = root.querySelector("[data-vb-uploadsim-count]");
      const status = root.querySelector("[data-vb-uploadsim-status]");

      let totalFiles = 0;

      function prettySize(bytes) {
        if (bytes < 1024) return bytes + " B";
        if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + " KB";
        return (bytes / 1024 / 1024).toFixed(1) + " MB";
      }

      function updateCount() {
        count.textContent = totalFiles === 1 ? "1 file" : totalFiles + " files";
        if (empty) empty.style.display = totalFiles ? "none" : "block";
      }

      function addFile(file) {
        totalFiles += 1;
        updateCount();

        const item = document.createElement("article");
        item.className = "vb-uploadsim-file";

        item.innerHTML =
          '<div class="vb-uploadsim-file-top">' +
          '<strong>' + file.name + '</strong>' +
          '<span>0%</span>' +
          '</div>' +
          '<div class="vb-uploadsim-bar"><div class="vb-uploadsim-fill"></div></div>';

        list.appendChild(item);

        const label = item.querySelector("span");
        const fill = item.querySelector(".vb-uploadsim-fill");
        let progress = 0;

        status.textContent = "Simulating upload: " + file.name + " (" + prettySize(file.size) + ")";

        const timer = setInterval(function () {
          progress += Math.floor(Math.random() * 18) + 7;
          if (progress >= 100) {
            progress = 100;
            clearInterval(timer);
          }

          label.textContent = progress + "%";
          fill.style.width = progress + "%";

          if (progress === 100) {
            label.textContent = "Done";
            status.textContent = "Upload simulation complete.";
          }
        }, 240);
      }

      function addFiles(files) {
        Array.from(files).forEach(addFile);
      }

      browse.addEventListener("click", function () {
        input.click();
      });

      input.addEventListener("change", function () {
        addFiles(input.files);
        input.value = "";
      });

      zone.addEventListener("dragover", function (event) {
        event.preventDefault();
        zone.classList.add("is-over");
      });

      zone.addEventListener("dragleave", function () {
        zone.classList.remove("is-over");
      });

      zone.addEventListener("drop", function (event) {
        event.preventDefault();
        zone.classList.remove("is-over");
        addFiles(event.dataTransfer.files);
      });

      updateCount();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initUploadSim);
  } else {
    initUploadSim();
  }
})();

HTML

<div class="vb-uploadsim-demo">
  <div class="vb-uploadsim-wrap" data-vb-uploadsim>
    <div class="vb-uploadsim-head">
      <span>Example 25</span>
      <h3>Upload with Progress Simulation</h3>
      <p>Drop files into the upload zone or click the button. Progress is simulated in the browser.</p>
    </div>

    <div class="vb-uploadsim-layout">
      <section class="vb-uploadsim-zone" data-vb-uploadsim-zone>
        <input type="file" multiple data-vb-uploadsim-input>
        <div class="vb-uploadsim-icon">↑</div>
        <h4>Drop files here</h4>
        <p>Images, PDFs, documents or project files</p>
        <button type="button" data-vb-uploadsim-browse>Choose files</button>
      </section>

      <section class="vb-uploadsim-panel">
        <div class="vb-uploadsim-panel-head">
          <div>
            <span>Upload Queue</span>
            <h4>Files</h4>
          </div>
          <strong data-vb-uploadsim-count>0 files</strong>
        </div>

        <div class="vb-uploadsim-list" data-vb-uploadsim-list>
          <p data-vb-uploadsim-empty>No files added yet.</p>
        </div>
      </section>
    </div>

    <div class="vb-uploadsim-status" data-vb-uploadsim-status>Drag files into the upload zone.</div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript drag and drop upload simulation is useful for file upload forms, media dashboards, document portals, admin panels, SaaS apps, and project management tools.

26. Drag and Drop Card Stack Swipe UI

A card stack swipe UI lets users drag the top card left or right to make a quick decision. This pattern is useful for onboarding screens, product recommendations, candidate review tools, idea voting, content approval interfaces, and mobile-style web apps.

Example 26

Card Stack Swipe UI

Drag the top card left to reject or right to approve. The next card appears automatically.

Project Idea

Website redesign

Refresh the homepage, service pages, and contact flow with a modern visual system.

Product Idea

AI chatbot

Add a custom trained chatbot that answers customer questions and suggests products.

Growth Idea

SEO content hub

Create a large content hub with examples, tutorials, internal links, and lead capture.

← Reject Approve →

JavaScript

(function () {
  function initCardStack() {
    document.querySelectorAll("[data-vb-cardstack]").forEach(function (root) {
      if (root.getAttribute("data-vb-cardstack-ready") === "1") return;
      root.setAttribute("data-vb-cardstack-ready", "1");

      const deck = root.querySelector("[data-vb-cardstack-deck]");
      const approvedEl = root.querySelector("[data-vb-cardstack-approved]");
      const rejectedEl = root.querySelector("[data-vb-cardstack-rejected]");
      const status = root.querySelector("[data-vb-cardstack-status]");
      const reset = root.querySelector("[data-vb-cardstack-reset]");

      const original = deck.innerHTML;
      let active = null;
      let startX = 0;
      let startY = 0;
      let currentX = 0;
      let currentY = 0;
      let approved = 0;
      let rejected = 0;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function topCard() {
        return deck.querySelector("[data-vb-cardstack-card]");
      }

      function updateScores() {
        approvedEl.textContent = approved;
        rejectedEl.textContent = rejected;
      }

      function start(event) {
        const card = event.target.closest("[data-vb-cardstack-card]");
        if (!card || card !== topCard()) return;

        event.preventDefault();

        const point = getPoint(event);
        active = card;
        startX = point.x;
        startY = point.y;
        currentX = 0;
        currentY = 0;
        active.classList.add("is-dragging");

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        currentX = point.x - startX;
        currentY = point.y - startY;

        const rotate = currentX / 18;
        active.style.transform = "translate(" + currentX + "px, " + currentY + "px) rotate(" + rotate + "deg)";
      }

      function removeCard(direction) {
        const title = active.getAttribute("data-title");

        if (direction === "right") {
          approved += 1;
          status.textContent = "Approved: " + title;
          active.style.transform = "translate(130%, " + currentY + "px) rotate(18deg)";
        } else {
          rejected += 1;
          status.textContent = "Rejected: " + title;
          active.style.transform = "translate(-130%, " + currentY + "px) rotate(-18deg)";
        }

        active.style.opacity = "0";
        updateScores();

        const removed = active;
        setTimeout(function () {
          removed.remove();
          if (!topCard()) status.textContent = "All cards reviewed. Use reset to start again.";
        }, 230);
      }

      function end() {
        if (!active) return;

        if (currentX > 110) {
          removeCard("right");
        } else if (currentX < -110) {
          removeCard("left");
        } else {
          active.style.transform = "";
        }

        cleanup();
      }

      function cancel() {
        if (active) active.style.transform = "";
        cleanup();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");

        active = null;

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      reset.addEventListener("click", function () {
        deck.innerHTML = original;
        approved = 0;
        rejected = 0;
        updateScores();
        status.textContent = "Drag the top card to start.";
      });

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateScores();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initCardStack);
  } else {
    initCardStack();
  }
})();

HTML

<div class="vb-cardstack-demo">
  <div class="vb-cardstack-wrap" data-vb-cardstack>
    <div class="vb-cardstack-head">
      <span>Example 26</span>
      <h3>Card Stack Swipe UI</h3>
      <p>Drag the top card left to reject or right to approve. The next card appears automatically.</p>
    </div>

    <div class="vb-cardstack-layout">
      <section class="vb-cardstack-area">
        <div class="vb-cardstack-deck" data-vb-cardstack-deck>
          <article class="vb-cardstack-card" data-vb-cardstack-card data-title="Website redesign" data-index="0">
            <span>Project Idea</span>
            <h4>Website redesign</h4>
            <p>Refresh the homepage, service pages, and contact flow with a modern visual system.</p>
          </article>

          <article class="vb-cardstack-card" data-vb-cardstack-card data-title="AI chatbot" data-index="1">
            <span>Product Idea</span>
            <h4>AI chatbot</h4>
            <p>Add a custom trained chatbot that answers customer questions and suggests products.</p>
          </article>

          <article class="vb-cardstack-card" data-vb-cardstack-card data-title="SEO content hub" data-index="2">
            <span>Growth Idea</span>
            <h4>SEO content hub</h4>
            <p>Create a large content hub with examples, tutorials, internal links, and lead capture.</p>
          </article>
        </div>

        <div class="vb-cardstack-actions">
          <span>← Reject</span>
          <span>Approve →</span>
        </div>
      </section>

      <aside class="vb-cardstack-score">
        <span>Decision Summary</span>
        <strong><b data-vb-cardstack-approved>0</b> approved</strong>
        <strong><b data-vb-cardstack-rejected>0</b> rejected</strong>
        <p data-vb-cardstack-status>Drag the top card to start.</p>
        <button type="button" data-vb-cardstack-reset>Reset stack</button>
      </aside>
    </div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript drag and drop card stack swipe UI is useful for onboarding screens, product recommendation tools, content approval systems, candidate review dashboards, and mobile-style web apps.

27. Drag and Drop Map Pin Placement UI

A drag and drop map pin placement UI lets users place a marker on a visual map area. This pattern is useful for location forms, delivery zone tools, property dashboards, event map builders, store locator interfaces, and admin map editors.

Example 27

Map Pin Placement UI

Drag the map pin to choose a location. The relative map coordinates update instantly.

JavaScript

(function () {
  function initMapDrop() {
    document.querySelectorAll("[data-vb-mapdrop]").forEach(function (root) {
      if (root.getAttribute("data-vb-mapdrop-ready") === "1") return;
      root.setAttribute("data-vb-mapdrop-ready", "1");

      const map = root.querySelector("[data-vb-mapdrop-map]");
      const pin = root.querySelector("[data-vb-mapdrop-pin]");
      const output = root.querySelector("[data-vb-mapdrop-output]");
      const reset = root.querySelector("[data-vb-mapdrop-reset]");

      let active = false;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function setPinByPercent(xPercent, yPercent) {
        xPercent = Math.max(3, Math.min(97, xPercent));
        yPercent = Math.max(8, Math.min(98, yPercent));

        pin.style.left = xPercent + "%";
        pin.style.top = yPercent + "%";
        output.textContent = "X: " + Math.round(xPercent) + "%, Y: " + Math.round(yPercent) + "%";
      }

      function setPinByPoint(x, y) {
        const rect = map.getBoundingClientRect();
        const xPercent = ((x - rect.left) / rect.width) * 100;
        const yPercent = ((y - rect.top) / rect.height) * 100;
        setPinByPercent(xPercent, yPercent);
      }

      function start(event) {
        if (!event.target.closest("[data-vb-mapdrop-pin]")) return;

        event.preventDefault();
        active = true;
        pin.classList.add("is-dragging");

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", end);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        setPinByPoint(point.x, point.y);
      }

      function end() {
        active = false;
        pin.classList.remove("is-dragging");

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", end);
      }

      reset.addEventListener("click", function () {
        setPinByPercent(50, 50);
      });

      pin.addEventListener("mousedown", start);
      pin.addEventListener("touchstart", start, { passive: false });
      setPinByPercent(50, 50);
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initMapDrop);
  } else {
    initMapDrop();
  }
})();

HTML

<div class="vb-mapdrop-demo">
  <div class="vb-mapdrop-wrap" data-vb-mapdrop>
    <div class="vb-mapdrop-head">
      <span>Example 27</span>
      <h3>Map Pin Placement UI</h3>
      <p>Drag the map pin to choose a location. The relative map coordinates update instantly.</p>
    </div>

    <div class="vb-mapdrop-layout">
      <section class="vb-mapdrop-map" data-vb-mapdrop-map>
        <div class="vb-mapdrop-road vb-mapdrop-road-one"></div>
        <div class="vb-mapdrop-road vb-mapdrop-road-two"></div>
        <div class="vb-mapdrop-block vb-mapdrop-block-a"></div>
        <div class="vb-mapdrop-block vb-mapdrop-block-b"></div>
        <div class="vb-mapdrop-block vb-mapdrop-block-c"></div>

        <button type="button" class="vb-mapdrop-pin" data-vb-mapdrop-pin aria-label="Move map pin">
          <span></span>
        </button>
      </section>

      <aside class="vb-mapdrop-panel">
        <span>Selected Position</span>
        <strong data-vb-mapdrop-output>X: 50%, Y: 50%</strong>
        <p>This demo uses relative percentage coordinates, so the pin stays responsive inside the map area.</p>
        <button type="button" data-vb-mapdrop-reset>Reset pin</button>
      </aside>
    </div>
  </div>
</div>

CSS

/* CSS is included fully in the live demo block above. */

This JavaScript map pin placement UI is useful for location forms, delivery zone tools, property dashboards, event map builders, store locator interfaces, and admin map editors.

28. Mobile-Friendly Touch Drag and Drop List

A mobile-friendly touch drag and drop list is useful when users need to reorder tasks, checklist items, priorities, lessons, steps, or content blocks on phones and tablets. This example uses mouse and touch events, a floating drag preview, automatic reordering, and a live order summary.

Example 28

Mobile Touch Sort List

Drag the handle on each card to reorder the mobile task list.

Today Priority List

Plan homepage layout

Define sections and CTA order

Write service copy

Create short conversion text

Design contact form

Make it fast and simple

Test mobile view

Check spacing and buttons

Plan homepage layout → Write service copy → Design contact form → Test mobile view

JavaScript

(function () {
  function initTouchSort() {
    document.querySelectorAll("[data-vb-touchsort]").forEach(function (root) {
      if (root.getAttribute("data-vb-touchsort-ready") === "1") return;
      root.setAttribute("data-vb-touchsort-ready", "1");

      const list = root.querySelector("[data-vb-touchsort-list]");
      const output = root.querySelector("[data-vb-touchsort-output]");
      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateOutput() {
        output.textContent = Array.from(list.querySelectorAll("[data-vb-touchsort-item]"))
          .map(function (item) { return item.getAttribute("data-title"); })
          .join(" → ");
      }

      function createGhost(item, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-touchsort-ghost";
        ghost.innerHTML = "<strong>" + item.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 12 + "px";
        ghost.style.top = y + 12 + "px";
      }

      function clearOver() {
        list.querySelectorAll(".is-over").forEach(function (item) {
          item.classList.remove("is-over");
        });
      }

      function reorderAt(x, y) {
        const element = document.elementFromPoint(x, y);
        const target = element ? element.closest("[data-vb-touchsort-item]") : null;

        if (!target || target === active || !list.contains(target)) return;

        const rect = target.getBoundingClientRect();
        const after = y > rect.top + rect.height / 2;

        clearOver();
        target.classList.add("is-over");

        if (after) {
          target.insertAdjacentElement("afterend", active);
        } else {
          target.insertAdjacentElement("beforebegin", active);
        }

        updateOutput();
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-touchsort-handle]");
        if (!handle) return;

        const item = handle.closest("[data-vb-touchsort-item]");
        if (!item) return;

        event.preventDefault();

        const point = getPoint(event);
        active = item;
        active.classList.add("is-dragging");
        createGhost(item, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        reorderAt(point.x, point.y);
      }

      function end() {
        cleanup();
        updateOutput();
      }

      function cancel() {
        cleanup();
        updateOutput();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateOutput();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initTouchSort);
  } else {
    initTouchSort();
  }
})();

HTML

<div class="vb-touchsort-demo">
  <div class="vb-touchsort-wrap" data-vb-touchsort>
    <div class="vb-touchsort-head">
      <span>Example 28</span>
      <h3>Mobile Touch Sort List</h3>
      <p>Drag the handle on each card to reorder the mobile task list.</p>
    </div>

    <div class="vb-touchsort-phone">
      <div class="vb-touchsort-phone-top">
        <span>Today</span>
        <strong>Priority List</strong>
      </div>

      <div class="vb-touchsort-list" data-vb-touchsort-list>
        <article class="vb-touchsort-item" data-vb-touchsort-item data-title="Plan homepage layout">
          <button type="button" data-vb-touchsort-handle aria-label="Drag item">⋮⋮</button>
          <div>
            <h4>Plan homepage layout</h4>
            <p>Define sections and CTA order</p>
          </div>
        </article>

        <article class="vb-touchsort-item" data-vb-touchsort-item data-title="Write service copy">
          <button type="button" data-vb-touchsort-handle aria-label="Drag item">⋮⋮</button>
          <div>
            <h4>Write service copy</h4>
            <p>Create short conversion text</p>
          </div>
        </article>

        <article class="vb-touchsort-item" data-vb-touchsort-item data-title="Design contact form">
          <button type="button" data-vb-touchsort-handle aria-label="Drag item">⋮⋮</button>
          <div>
            <h4>Design contact form</h4>
            <p>Make it fast and simple</p>
          </div>
        </article>

        <article class="vb-touchsort-item" data-vb-touchsort-item data-title="Test mobile view">
          <button type="button" data-vb-touchsort-handle aria-label="Drag item">⋮⋮</button>
          <div>
            <h4>Test mobile view</h4>
            <p>Check spacing and buttons</p>
          </div>
        </article>
      </div>

      <div class="vb-touchsort-output" data-vb-touchsort-output>Plan homepage layout → Write service copy → Design contact form → Test mobile view</div>
    </div>
  </div>
</div>

CSS

.vb-touchsort-demo,
.vb-touchsort-demo * {
  box-sizing: border-box;
}

.vb-touchsort-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(14, 165, 233, 0.16), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #ecfeff 0%, #eef2ff 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-touchsort-wrap {
  max-width: 960px;
  margin: 0 auto;
}

.vb-touchsort-head {
  max-width: 760px;
  margin: 0 auto 24px;
  text-align: center;
}

.vb-touchsort-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #cffafe;
  color: #0e7490 !important;
  -webkit-text-fill-color: #0e7490 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-touchsort-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-touchsort-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-touchsort-phone {
  max-width: 430px;
  margin: 0 auto;
  padding: 16px;
  border-radius: 36px;
  background: #0f172a;
  box-shadow: 0 26px 80px rgba(15, 23, 42, 0.25);
}

.vb-touchsort-phone-top {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  padding: 14px 14px 18px;
}

.vb-touchsort-phone-top span {
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-touchsort-phone-top strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
}

.vb-touchsort-list {
  display: grid;
  gap: 10px;
}

.vb-touchsort-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 13px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: 0 12px 26px rgba(0, 0, 0, 0.18);
}

.vb-touchsort-item.is-dragging {
  opacity: 0.38;
}

.vb-touchsort-item.is-over {
  outline: 3px solid rgba(34, 211, 238, 0.45);
}

.vb-touchsort-item button {
  width: 38px;
  height: 44px;
  border: 0;
  border-radius: 15px;
  background: #cffafe;
  color: #0e7490 !important;
  -webkit-text-fill-color: #0e7490 !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
}

.vb-touchsort-item h4 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px !important;
  line-height: 1.22 !important;
  font-weight: 950 !important;
}

.vb-touchsort-item p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  line-height: 1.45;
  font-weight: 700;
}

.vb-touchsort-output {
  margin-top: 12px;
  padding: 13px;
  border-radius: 20px;
  background: rgba(255,255,255,0.08);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
  line-height: 1.55;
  font-weight: 800;
}

.vb-touchsort-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(340px, calc(100vw - 28px));
  padding: 13px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(14, 165, 233, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.26);
}

.vb-touchsort-ghost strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px;
  font-weight: 950;
}

@media (max-width: 520px) {
  .vb-touchsort-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-touchsort-phone {
    border-radius: 28px;
  }
}

This JavaScript mobile-friendly touch drag and drop list is useful for task apps, priority lists, mobile dashboards, checklist builders, lesson ordering, and responsive admin interfaces.

29. Accessible Drag and Drop List with Keyboard Controls

An accessible drag and drop list should not rely only on pointer movement. This example adds keyboard controls so users can select an item, move it up or down, and understand the current order through a live status message.

Example 29

Accessible Reorder List

Use drag handles, or use the up and down buttons to reorder each item without dragging.

1

Account setup

Create user account and verify email.

2

Choose template

Select a design or starting layout.

3

Add content

Write page copy and add images.

4

Publish page

Review, test, and publish online.

Current order: Account setup → Choose template → Add content → Publish page

JavaScript

(function () {
  function initA11ySort() {
    document.querySelectorAll("[data-vb-a11ysort]").forEach(function (root) {
      if (root.getAttribute("data-vb-a11ysort-ready") === "1") return;
      root.setAttribute("data-vb-a11ysort-ready", "1");

      const list = root.querySelector("[data-vb-a11ysort-list]");
      const status = root.querySelector("[data-vb-a11ysort-status]");
      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function getItems() {
        return Array.from(list.querySelectorAll("[data-vb-a11ysort-item]"));
      }

      function updateStatus(message) {
        getItems().forEach(function (item, index) {
          const number = item.querySelector("[data-vb-a11ysort-number]");
          if (number) number.textContent = index + 1;
        });

        const order = getItems().map(function (item) {
          return item.getAttribute("data-title");
        }).join(" → ");

        status.textContent = message ? message + " Current order: " + order : "Current order: " + order;
      }

      function moveItem(item, direction) {
        if (direction === "up" && item.previousElementSibling) {
          list.insertBefore(item, item.previousElementSibling);
          updateStatus(item.getAttribute("data-title") + " moved up.");
        }

        if (direction === "down" && item.nextElementSibling) {
          list.insertBefore(item.nextElementSibling, item);
          updateStatus(item.getAttribute("data-title") + " moved down.");
        }
      }

      function createGhost(item, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-a11ysort-ghost";
        ghost.innerHTML = "<strong>" + item.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 12 + "px";
        ghost.style.top = y + 12 + "px";
      }

      function clearOver() {
        list.querySelectorAll(".is-over").forEach(function (item) {
          item.classList.remove("is-over");
        });
      }

      function reorderAt(x, y) {
        const element = document.elementFromPoint(x, y);
        const target = element ? element.closest("[data-vb-a11ysort-item]") : null;

        if (!target || target === active || !list.contains(target)) return;

        const rect = target.getBoundingClientRect();
        const after = y > rect.top + rect.height / 2;

        clearOver();
        target.classList.add("is-over");

        if (after) {
          target.insertAdjacentElement("afterend", active);
        } else {
          target.insertAdjacentElement("beforebegin", active);
        }

        updateStatus("List reordered.");
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-a11ysort-handle]");
        if (!handle) return;

        const item = handle.closest("[data-vb-a11ysort-item]");
        if (!item) return;

        event.preventDefault();

        const point = getPoint(event);
        active = item;
        active.classList.add("is-dragging");
        createGhost(item, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        reorderAt(point.x, point.y);
      }

      function end() {
        cleanup();
        updateStatus("Drag reorder finished.");
      }

      function cancel() {
        cleanup();
        updateStatus("Drag cancelled.");
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("click", function (event) {
        const up = event.target.closest("[data-vb-a11ysort-up]");
        const down = event.target.closest("[data-vb-a11ysort-down]");

        if (up) moveItem(up.closest("[data-vb-a11ysort-item]"), "up");
        if (down) moveItem(down.closest("[data-vb-a11ysort-item]"), "down");
      });

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateStatus();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initA11ySort);
  } else {
    initA11ySort();
  }
})();

HTML

<div class="vb-a11ysort-demo">
  <div class="vb-a11ysort-wrap" data-vb-a11ysort>
    <div class="vb-a11ysort-head">
      <span>Example 29</span>
      <h3>Accessible Reorder List</h3>
      <p>Use drag handles, or use the up and down buttons to reorder each item without dragging.</p>
    </div>

    <div class="vb-a11ysort-card">
      <div class="vb-a11ysort-list" data-vb-a11ysort-list>
        <article class="vb-a11ysort-item" data-vb-a11ysort-item data-title="Account setup">
          <button type="button" class="vb-a11ysort-handle" data-vb-a11ysort-handle aria-label="Drag Account setup">⋮⋮</button>
          <span data-vb-a11ysort-number>1</span>
          <div>
            <h4>Account setup</h4>
            <p>Create user account and verify email.</p>
          </div>
          <div class="vb-a11ysort-actions">
            <button type="button" data-vb-a11ysort-up aria-label="Move Account setup up">↑</button>
            <button type="button" data-vb-a11ysort-down aria-label="Move Account setup down">↓</button>
          </div>
        </article>

        <article class="vb-a11ysort-item" data-vb-a11ysort-item data-title="Choose template">
          <button type="button" class="vb-a11ysort-handle" data-vb-a11ysort-handle aria-label="Drag Choose template">⋮⋮</button>
          <span data-vb-a11ysort-number>2</span>
          <div>
            <h4>Choose template</h4>
            <p>Select a design or starting layout.</p>
          </div>
          <div class="vb-a11ysort-actions">
            <button type="button" data-vb-a11ysort-up aria-label="Move Choose template up">↑</button>
            <button type="button" data-vb-a11ysort-down aria-label="Move Choose template down">↓</button>
          </div>
        </article>

        <article class="vb-a11ysort-item" data-vb-a11ysort-item data-title="Add content">
          <button type="button" class="vb-a11ysort-handle" data-vb-a11ysort-handle aria-label="Drag Add content">⋮⋮</button>
          <span data-vb-a11ysort-number>3</span>
          <div>
            <h4>Add content</h4>
            <p>Write page copy and add images.</p>
          </div>
          <div class="vb-a11ysort-actions">
            <button type="button" data-vb-a11ysort-up aria-label="Move Add content up">↑</button>
            <button type="button" data-vb-a11ysort-down aria-label="Move Add content down">↓</button>
          </div>
        </article>

        <article class="vb-a11ysort-item" data-vb-a11ysort-item data-title="Publish page">
          <button type="button" class="vb-a11ysort-handle" data-vb-a11ysort-handle aria-label="Drag Publish page">⋮⋮</button>
          <span data-vb-a11ysort-number>4</span>
          <div>
            <h4>Publish page</h4>
            <p>Review, test, and publish online.</p>
          </div>
          <div class="vb-a11ysort-actions">
            <button type="button" data-vb-a11ysort-up aria-label="Move Publish page up">↑</button>
            <button type="button" data-vb-a11ysort-down aria-label="Move Publish page down">↓</button>
          </div>
        </article>
      </div>

      <div class="vb-a11ysort-status" data-vb-a11ysort-status aria-live="polite">
        Current order: Account setup → Choose template → Add content → Publish page
      </div>
    </div>
  </div>
</div>

CSS

.vb-a11ysort-demo,
.vb-a11ysort-demo * {
  box-sizing: border-box;
}

.vb-a11ysort-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(168, 85, 247, 0.15), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(14, 165, 233, 0.15), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #ecfeff 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-a11ysort-wrap {
  max-width: 1040px;
  margin: 0 auto;
}

.vb-a11ysort-head {
  max-width: 800px;
  margin-bottom: 22px;
}

.vb-a11ysort-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #f3e8ff;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-a11ysort-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-a11ysort-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-a11ysort-card {
  padding: 16px;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}

.vb-a11ysort-list {
  display: grid;
  gap: 12px;
}

.vb-a11ysort-item {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 14px;
  border-radius: 22px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.26);
}

.vb-a11ysort-item.is-dragging {
  opacity: 0.42;
}

.vb-a11ysort-item.is-over {
  outline: 3px solid rgba(168, 85, 247, 0.30);
}

.vb-a11ysort-handle {
  width: 38px;
  height: 44px;
  border: 0;
  border-radius: 14px;
  background: #f3e8ff;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 20px;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
}

.vb-a11ysort-item > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-a11ysort-item h4 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px !important;
  line-height: 1.22 !important;
  font-weight: 950 !important;
}

.vb-a11ysort-item p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.45;
  font-weight: 700;
}

.vb-a11ysort-actions {
  display: flex;
  gap: 8px;
}

.vb-a11ysort-actions button {
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 13px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
}

.vb-a11ysort-actions button:focus,
.vb-a11ysort-handle:focus {
  outline: 3px solid rgba(14, 165, 233, 0.65);
  outline-offset: 2px;
}

.vb-a11ysort-status {
  margin-top: 14px;
  padding: 14px 16px;
  border-radius: 18px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 800;
}

.vb-a11ysort-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(340px, calc(100vw - 28px));
  padding: 13px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(168, 85, 247, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.26);
}

.vb-a11ysort-ghost strong {
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 14px;
  font-weight: 950;
}

@media (max-width: 720px) {
  .vb-a11ysort-item {
    grid-template-columns: auto auto minmax(0, 1fr);
  }

  .vb-a11ysort-actions {
    grid-column: 1 / -1;
    justify-content: flex-end;
  }
}

@media (max-width: 480px) {
  .vb-a11ysort-item > span {
    display: none;
  }

  .vb-a11ysort-item {
    grid-template-columns: auto minmax(0, 1fr);
  }
}

This accessible JavaScript drag and drop list is useful for admin dashboards, step ordering tools, onboarding builders, content workflows, accessibility-focused forms, and keyboard-friendly web interfaces.

30. Complete Responsive Drag and Drop Project Planner

A complete responsive drag and drop project planner combines multiple practical ideas into one stronger UI: task columns, drag and drop cards, live counters, priority labels, and a project summary. It is useful for project dashboards, editorial calendars, agency workflows, client portals, team task boards, and SaaS productivity tools.

Example 30

Responsive Project Planner

Drag cards between columns to update the project status. Counters and summary update automatically.

Total tasks 6
In progress 0
Done 0

Backlog

0
Design
Create landing page wireframe

Plan hero, sections, CTA flow and mobile structure.

Content
Write SEO page copy

Create clear headings, intro text and service descriptions.

In Progress

0
Frontend
Build responsive CSS layout

Make the layout work on desktop, tablet and mobile.

Forms
Connect quote request form

Send form data to email and save request details.

Review

0
QA
Test contact page

Check validation, email delivery and mobile spacing.

Done

0
Tracking
Install analytics

Add tracking and conversion events.

Drag a task card between project columns.

JavaScript

(function () {
  function initProjectPlanner() {
    document.querySelectorAll("[data-vb-projectplan]").forEach(function (root) {
      if (root.getAttribute("data-vb-projectplan-ready") === "1") return;
      root.setAttribute("data-vb-projectplan-ready", "1");

      const drops = Array.from(root.querySelectorAll("[data-vb-projectplan-drop]"));
      const total = root.querySelector("[data-vb-projectplan-total]");
      const status = root.querySelector("[data-vb-projectplan-status]");
      let active = null;
      let ghost = null;

      function getPoint(event) {
        if (event.touches && event.touches.length) return { x: event.touches[0].clientX, y: event.touches[0].clientY };
        if (event.changedTouches && event.changedTouches.length) return { x: event.changedTouches[0].clientX, y: event.changedTouches[0].clientY };
        return { x: event.clientX, y: event.clientY };
      }

      function updateCounts() {
        const allCards = root.querySelectorAll("[data-vb-projectplan-card]");
        total.textContent = allCards.length;

        drops.forEach(function (drop) {
          const column = drop.getAttribute("data-column");
          const countEls = root.querySelectorAll('[data-vb-projectplan-column-count="' + column + '"]');
          countEls.forEach(function (countEl) {
            countEl.textContent = drop.querySelectorAll("[data-vb-projectplan-card]").length;
          });
        });
      }

      function getDropAt(x, y) {
        const element = document.elementFromPoint(x, y);
        if (!element) return null;
        const drop = element.closest("[data-vb-projectplan-drop]");
        return drops.includes(drop) ? drop : null;
      }

      function clearOver() {
        drops.forEach(function (drop) {
          drop.classList.remove("is-over");
        });
      }

      function createGhost(card, x, y) {
        ghost = document.createElement("div");
        ghost.className = "vb-projectplan-ghost";
        ghost.innerHTML = "<strong>" + card.getAttribute("data-title") + "</strong>";
        document.body.appendChild(ghost);
        moveGhost(x, y);
      }

      function moveGhost(x, y) {
        if (!ghost) return;
        ghost.style.left = x + 12 + "px";
        ghost.style.top = y + 12 + "px";
      }

      function placeCardInDrop(drop, x, y) {
        const cards = Array.from(drop.querySelectorAll("[data-vb-projectplan-card]")).filter(function (card) {
          return card !== active;
        });

        let placed = false;

        cards.forEach(function (card) {
          if (placed) return;

          const rect = card.getBoundingClientRect();
          if (y < rect.top + rect.height / 2) {
            drop.insertBefore(active, card);
            placed = true;
          }
        });

        if (!placed) {
          drop.appendChild(active);
        }
      }

      function start(event) {
        const handle = event.target.closest("[data-vb-projectplan-handle]");
        if (!handle) return;

        const card = handle.closest("[data-vb-projectplan-card]");
        if (!card) return;

        event.preventDefault();

        const point = getPoint(event);
        active = card;
        active.classList.add("is-dragging");
        createGhost(card, point.x, point.y);

        document.addEventListener("mousemove", move);
        document.addEventListener("mouseup", end);
        document.addEventListener("touchmove", move, { passive: false });
        document.addEventListener("touchend", end);
        document.addEventListener("touchcancel", cancel);
      }

      function move(event) {
        if (!active) return;

        event.preventDefault();

        const point = getPoint(event);
        moveGhost(point.x, point.y);
        clearOver();

        const drop = getDropAt(point.x, point.y);
        if (drop) {
          drop.classList.add("is-over");
          placeCardInDrop(drop, point.x, point.y);
          updateCounts();
        }
      }

      function end(event) {
        if (!active) return;

        const point = getPoint(event);
        const drop = getDropAt(point.x, point.y);

        if (drop) {
          status.textContent = active.getAttribute("data-title") + " moved to " + drop.getAttribute("data-column") + ".";
        }

        cleanup();
        updateCounts();
      }

      function cancel() {
        cleanup();
        updateCounts();
      }

      function cleanup() {
        if (active) active.classList.remove("is-dragging");
        if (ghost) ghost.remove();

        active = null;
        ghost = null;
        clearOver();

        document.removeEventListener("mousemove", move);
        document.removeEventListener("mouseup", end);
        document.removeEventListener("touchmove", move);
        document.removeEventListener("touchend", end);
        document.removeEventListener("touchcancel", cancel);
      }

      root.addEventListener("mousedown", start);
      root.addEventListener("touchstart", start, { passive: false });
      updateCounts();
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initProjectPlanner);
  } else {
    initProjectPlanner();
  }
})();

HTML

<div class="vb-projectplan-demo">
  <div class="vb-projectplan-wrap" data-vb-projectplan>
    <div class="vb-projectplan-head">
      <span>Example 30</span>
      <h3>Responsive Project Planner</h3>
      <p>Drag cards between columns to update the project status. Counters and summary update automatically.</p>
    </div>

    <div class="vb-projectplan-summary">
      <div>
        <span>Total tasks</span>
        <strong data-vb-projectplan-total>6</strong>
      </div>
      <div>
        <span>In progress</span>
        <strong data-vb-projectplan-column-count="progress">0</strong>
      </div>
      <div>
        <span>Done</span>
        <strong data-vb-projectplan-column-count="done">0</strong>
      </div>
    </div>

    <div class="vb-projectplan-board">
      <section class="vb-projectplan-column">
        <div class="vb-projectplan-column-head">
          <h4>Backlog</h4>
          <strong data-vb-projectplan-column-count="backlog">0</strong>
        </div>

        <div class="vb-projectplan-drop" data-vb-projectplan-drop data-column="backlog">
          <article class="vb-projectplan-card" data-vb-projectplan-card data-title="Create landing page wireframe">
            <button type="button" data-vb-projectplan-handle aria-label="Drag task">⋮⋮</button>
            <div>
              <span class="vb-projectplan-pill">Design</span>
              <h5>Create landing page wireframe</h5>
              <p>Plan hero, sections, CTA flow and mobile structure.</p>
            </div>
          </article>

          <article class="vb-projectplan-card" data-vb-projectplan-card data-title="Write SEO page copy">
            <button type="button" data-vb-projectplan-handle aria-label="Drag task">⋮⋮</button>
            <div>
              <span class="vb-projectplan-pill vb-projectplan-pill-green">Content</span>
              <h5>Write SEO page copy</h5>
              <p>Create clear headings, intro text and service descriptions.</p>
            </div>
          </article>
        </div>
      </section>

      <section class="vb-projectplan-column">
        <div class="vb-projectplan-column-head">
          <h4>In Progress</h4>
          <strong data-vb-projectplan-column-count="progress">0</strong>
        </div>

        <div class="vb-projectplan-drop" data-vb-projectplan-drop data-column="progress">
          <article class="vb-projectplan-card" data-vb-projectplan-card data-title="Build responsive CSS layout">
            <button type="button" data-vb-projectplan-handle aria-label="Drag task">⋮⋮</button>
            <div>
              <span class="vb-projectplan-pill vb-projectplan-pill-blue">Frontend</span>
              <h5>Build responsive CSS layout</h5>
              <p>Make the layout work on desktop, tablet and mobile.</p>
            </div>
          </article>

          <article class="vb-projectplan-card" data-vb-projectplan-card data-title="Connect quote request form">
            <button type="button" data-vb-projectplan-handle aria-label="Drag task">⋮⋮</button>
            <div>
              <span class="vb-projectplan-pill vb-projectplan-pill-orange">Forms</span>
              <h5>Connect quote request form</h5>
              <p>Send form data to email and save request details.</p>
            </div>
          </article>
        </div>
      </section>

      <section class="vb-projectplan-column">
        <div class="vb-projectplan-column-head">
          <h4>Review</h4>
          <strong data-vb-projectplan-column-count="review">0</strong>
        </div>

        <div class="vb-projectplan-drop" data-vb-projectplan-drop data-column="review">
          <article class="vb-projectplan-card" data-vb-projectplan-card data-title="Test contact page">
            <button type="button" data-vb-projectplan-handle aria-label="Drag task">⋮⋮</button>
            <div>
              <span class="vb-projectplan-pill vb-projectplan-pill-purple">QA</span>
              <h5>Test contact page</h5>
              <p>Check validation, email delivery and mobile spacing.</p>
            </div>
          </article>
        </div>
      </section>

      <section class="vb-projectplan-column">
        <div class="vb-projectplan-column-head">
          <h4>Done</h4>
          <strong data-vb-projectplan-column-count="done">0</strong>
        </div>

        <div class="vb-projectplan-drop" data-vb-projectplan-drop data-column="done">
          <article class="vb-projectplan-card" data-vb-projectplan-card data-title="Install analytics">
            <button type="button" data-vb-projectplan-handle aria-label="Drag task">⋮⋮</button>
            <div>
              <span class="vb-projectplan-pill vb-projectplan-pill-green">Tracking</span>
              <h5>Install analytics</h5>
              <p>Add tracking and conversion events.</p>
            </div>
          </article>
        </div>
      </section>
    </div>

    <div class="vb-projectplan-status" data-vb-projectplan-status>Drag a task card between project columns.</div>
  </div>
</div>

CSS

.vb-projectplan-demo,
.vb-projectplan-demo * {
  box-sizing: border-box;
}

.vb-projectplan-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(59, 130, 246, 0.16), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(16, 185, 129, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfdf5 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-projectplan-wrap {
  max-width: 1240px;
  margin: 0 auto;
}

.vb-projectplan-head {
  max-width: 860px;
  margin-bottom: 22px;
}

.vb-projectplan-head span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-projectplan-head h3 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
}

.vb-projectplan-head p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-projectplan-summary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}

.vb-projectplan-summary div {
  padding: 16px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-projectplan-summary span {
  display: block;
  margin-bottom: 6px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.vb-projectplan-summary strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 30px;
  line-height: 1;
  font-weight: 950;
}

.vb-projectplan-board {
  display: grid;
  grid-template-columns: repeat(4, minmax(220px, 1fr));
  gap: 14px;
  overflow-x: auto;
  padding-bottom: 4px;
}

.vb-projectplan-column {
  min-width: 220px;
  padding: 14px;
  border-radius: 28px;
  background: rgba(255,255,255,0.78);
  border: 1px solid rgba(148, 163, 184, 0.26);
  box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08);
}

.vb-projectplan-column-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.vb-projectplan-column-head h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
  letter-spacing: -0.04em;
}

.vb-projectplan-column-head strong {
  display: inline-flex;
  min-width: 34px;
  height: 32px;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
  font-weight: 950;
}

.vb-projectplan-drop {
  display: grid;
  align-content: start;
  gap: 10px;
  min-height: 360px;
  padding: 10px;
  border-radius: 22px;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  background: #f8fafc;
}

.vb-projectplan-drop.is-over {
  border-color: rgba(37, 99, 235, 0.88);
  background: #eff6ff;
  box-shadow: inset 0 0 0 4px rgba(37, 99, 235, 0.10);
}

.vb-projectplan-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  align-items: flex-start;
  padding: 12px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 12px 26px rgba(15, 23, 42, 0.07);
}

.vb-projectplan-card.is-dragging {
  opacity: 0.42;
}

.vb-projectplan-card button {
  width: 34px;
  height: 42px;
  border: 0;
  border-radius: 13px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 18px;
  font-weight: 950;
  cursor: grab;
  touch-action: none;
}

.vb-projectplan-pill {
  display: inline-flex;
  margin-bottom: 8px;
  padding: 5px 9px;
  border-radius: 999px;
  background: #f3e8ff;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 11px;
  font-weight: 950;
}

.vb-projectplan-pill-green {
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
}

.vb-projectplan-pill-blue {
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
}

.vb-projectplan-pill-orange {
  background: #ffedd5;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
}

.vb-projectplan-pill-purple {
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
}

.vb-projectplan-card h5 {
  margin: 0 0 6px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px !important;
  line-height: 1.24 !important;
  font-weight: 950 !important;
}

.vb-projectplan-card p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  line-height: 1.5;
  font-weight: 700;
}

.vb-projectplan-status {
  margin-top: 16px;
  padding: 13px 15px;
  border-radius: 18px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-projectplan-ghost {
  position: fixed;
  z-index: 999999;
  pointer-events: none;
  width: min(320px, calc(100vw - 28px));
  padding: 13px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(37, 99, 235, 0.55);
  box-shadow: 0 24px 60px rgba(15, 23, 42, 0.26);
}

.vb-projectplan-ghost strong {
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 14px;
  font-weight: 950;
}

@media (max-width: 900px) {
  .vb-projectplan-summary {
    grid-template-columns: 1fr;
  }

  .vb-projectplan-board {
    grid-template-columns: repeat(4, minmax(250px, 1fr));
  }
}

@media (max-width: 520px) {
  .vb-projectplan-demo {
    padding: 16px;
    border-radius: 24px;
  }

  .vb-projectplan-column {
    min-width: 250px;
  }
}

This complete responsive JavaScript drag and drop project planner is useful for project dashboards, agency workflows, client portals, editorial calendars, team boards, productivity apps, and SaaS task management interfaces.

EntitySchema Pro WordPress structured data and entity SEO plugin
WordPress SEO Plugin

EntitySchema Pro

Build a stronger entity-based structured data system for WordPress, modern SEO, AI-readable content, schema markup, and better search visibility.

Sale price 89€
29€ Save 60€
View Plugin

JavaScript Drag and Drop Best Practices

JavaScript drag and drop interfaces work best when the user always understands what can be moved, where it can be dropped, and what changed after the drop action. A good drag and drop component should feel smooth, predictable, responsive, and safe to use.

For simple desktop-only features, the native HTML Drag and Drop API can be enough. For mobile-friendly components, custom pointer, mouse, and touch logic is often more reliable because touch support for native drag behavior is limited and inconsistent across devices. That is why many of the examples in this guide use controlled drag handles, custom floating previews, drop-zone detection, and scoped JavaScript logic.

For real production websites, also think about saving the final order or state. A sortable list may need to save item order in a database, a Kanban board may need to send updated card status to an API, and a dashboard builder may need to remember widget placement for each user. For that, drag and drop logic can be combined with JavaScript localStorage for simple browser storage or JavaScript Fetch API for server-side saving.

Responsive Drag and Drop UI Tips

Responsive drag and drop design is not only about making the layout smaller. A drag interface that works well on a large desktop screen can become difficult on a phone if the drag handle is too small, the drop zone is hidden, or the board requires too much horizontal movement.

On mobile screens, drag and drop should use larger touch targets, clear spacing, scrollable containers, and simple movement rules. A mobile task list should not require pixel-perfect dragging. A Kanban board may need horizontal scrolling. A file upload area should still include a normal file input button because some users may prefer tapping instead of dragging files.

Use large touch handles

Small icons are hard to drag on mobile. Use visible handles with enough width and height for touch users.

Keep drop zones visible

Users should not have to guess where a card, file, tag, widget, or task can be dropped.

Support scrolling layouts

Kanban boards, galleries, dashboards, and planners often need horizontal or vertical scrolling on smaller screens.

Provide fallback controls

Use buttons, inputs, or keyboard controls when dragging is not the easiest interaction for every user.

For the best mobile experience, test the feature on an actual phone, not only inside a desktop browser preview. Check whether the page scrolls correctly, whether dragging blocks unwanted page movement, whether touch targets are large enough, and whether the drop result is clear after the user releases the item.

Common JavaScript Drag and Drop Mistakes

Many drag and drop interfaces look good in a demo but become frustrating in real use. The most common mistakes happen when the interaction has weak feedback, no mobile support, no accessible alternative, or unclear drop logic.

A strong drag and drop UI should not surprise the user. When a card is moved, the new position should be obvious. When a file is dropped, the file name, preview, validation result, or progress state should appear. When a task moves to another column, the counter and status label should update immediately.

JavaScript Drag and Drop FAQ

What is JavaScript drag and drop?

JavaScript drag and drop is an interaction pattern that lets users move items on a web page. It can be used for sortable lists, Kanban boards, upload zones, image galleries, form builders, dashboard layouts, menu builders, carts, comparison tools, and project planners.

Should I use the HTML Drag and Drop API or custom JavaScript events?

The HTML Drag and Drop API is useful for simple desktop interactions and file drop zones. Custom mouse, touch, or pointer logic is often better for mobile-friendly sorting, cards, dashboards, touch lists, and complex UI layouts.

Does native drag and drop work well on mobile?

Native HTML drag and drop is not always reliable on touch devices. For mobile-friendly drag and drop, use larger drag handles, touch events, pointer events, floating previews, and clear drop feedback.

Can JavaScript drag and drop upload files to a server?

Dragging files into a browser can select and preview files, but real uploading requires server-side handling or an API endpoint. A front-end demo can simulate progress, but production uploads need validation, security checks, storage logic, and server-side processing.

How do I save the new order after drag and drop?

You can save order in the browser with localStorage for simple UI preferences. For real apps, send the updated order to a backend using fetch() and store it in a database.

How do I make drag and drop accessible?

Add non-drag alternatives such as move up/down buttons, clear focus styles, status messages with aria-live, keyboard controls, readable labels, and simple instructions. Users should not be forced to use only a mouse or touch drag gesture.

Why does my drag and drop code break inside WordPress?

WordPress pages often include many blocks, scripts, and repeated examples. Use unique class names, scoped selectors, wrapper attributes, and initialization guards so one demo does not affect another demo on the same page.

Can I use drag and drop without external libraries?

Yes. All examples in this guide use vanilla JavaScript. External libraries can help with advanced sorting and animation, but many practical drag and drop interfaces can be built with plain HTML, CSS, and JavaScript.

Conclusion

JavaScript drag and drop features can turn a normal website into a more interactive tool. They help users reorder tasks, organize galleries, move cards between columns, upload files, assign people, build forms, arrange widgets, compare products, save favorites, and manage project workflows directly inside the interface.

The 30 examples in this guide show how flexible vanilla JavaScript can be. You can build sortable task lists, Kanban boards, drag and drop file upload zones, image previews, shopping cart interactions, team assignment boards, timeline builders, tag organizers, touch-friendly mobile lists, accessible reorder lists, and complete project planners without relying on a heavy framework.

For the best results, keep every drag and drop component practical. Use clear handles, visible drop zones, strong visual feedback, responsive layouts, scoped code, duplicate protection, and accessible alternatives. When the feature affects real user data, save the final state with localStorage, an API request, or a backend database instead of relying only on the front-end interface.

Drag and drop is most powerful when it solves a real workflow problem. Use it where direct movement is faster than clicking through forms or settings, and avoid it when a simple button, select field, or checkbox would be easier for the user.

Continue learning with these related JavaScript and CSS examples. These guides pair well with drag and drop components because many real interfaces combine sorting, forms, validation, storage, filtering, modals, cards, navigation, and responsive layout patterns.

JavaScript date and time features are used in almost every practical website or web app. They can format dates, display live clocks, calculate countdowns, show booking times, compare deadlines, create calendar widgets, convert time zones, validate date inputs, generate timestamps, and make dashboards, forms, ecommerce pages, event pages, and booking systems feel more useful.

In this guide, you will find 30 JavaScript date and time examples for real website projects, including live clocks, countdown timers, date formatters, time zone converters, event countdowns, booking date pickers, delivery date calculators, working hour checkers, age calculators, calendar cards, deadline trackers, relative time labels, and reusable vanilla JavaScript date components.

This post focuses on practical JavaScript date and time logic, including the Date object, timestamps, Intl.DateTimeFormat, date formatting, time differences, countdown intervals, timezone display, weekday calculations, deadline checks, form date validation, calendar UI patterns, and copy-paste-ready HTML, CSS, and JavaScript examples. For related JavaScript UI features, you can also explore our JavaScript countdown timer examples, JavaScript form validation examples, JavaScript localStorage examples, and JavaScript calculator examples.

View All 30 JavaScript Date and Time Examples

What Is JavaScript Date and Time?

JavaScript date and time refers to the browser-based logic used to create, read, format, compare, and update dates and times on a website. The most common starting point is the built-in Date object, which can represent the current time, a selected date, a future deadline, a past timestamp, or a custom date value entered by the user.

Date and time features are important because websites often need to show information that changes with time. A landing page may need an event countdown, an ecommerce page may need a delivery estimate, a dashboard may need a live clock, a booking form may need date validation, and a blog or app interface may need relative labels like “today”, “yesterday”, or “3 days ago”.

JavaScript can also format dates for different languages and regions, calculate the difference between two dates, show weekdays, detect working hours, display local time, convert basic time zone views, and keep timers updating automatically. These small features can make a simple page feel more interactive, useful, and professional.

Why Date and Time Features Matter

Date and time features matter because users often make decisions based on timing. They want to know when a sale ends, when an event starts, how long delivery may take, whether a business is open, how much time remains before a deadline, or what date they selected in a form.

Date and time logic should still be handled carefully. Time zones, daylight saving time, invalid date inputs, browser differences, and formatting expectations can create mistakes if the logic is too simple. For most website UI components, the best approach is to keep the feature clear, test realistic dates, and display results in a format users understand immediately.

JavaScript Date and Time Use Cases

There are many practical JavaScript date and time use cases for websites, landing pages, ecommerce stores, dashboards, booking systems, blogs, and web apps. Some are small UI helpers, while others can become full interactive tools.

A simple date feature may only show the current year in the footer. A more advanced feature may calculate delivery dates, update a countdown every second, compare selected booking dates, generate a mini calendar, or display multiple time zones for an international audience.

This guide focuses on JavaScript date and time examples, so every demo will include visible JavaScript, HTML, and CSS code. The examples are designed to be copy-paste friendly, practical for real websites, and different in layout, logic, interface style, time calculation, UI behavior, and responsive design.

What Should a Good Date Time Component Include?

A good date and time component should be accurate, readable, and useful. Users should understand what the time means, the display should update when needed, and the interface should avoid confusing date formats or hidden assumptions.

Clear date purpose

The component should have a clear reason to show time, such as a deadline, event date, booking slot, delivery estimate, live clock, or relative timestamp.

Readable formatting

Dates should be displayed in a format users understand quickly, instead of raw timestamps or confusing numeric-only values.

Reliable time logic

The component should handle updates, expired states, invalid inputs, selected dates, and time differences in a predictable way.

Responsive UI design

Date cards, countdowns, calendars, and time panels should stay readable on mobile, tablet, and desktop screens.

Before building a JavaScript date or time feature, decide what the user needs to know. Is the goal to show the current time, count down to an event, calculate a date range, format an input, compare two dates, show available booking days, or create urgency for a sale? A clear purpose makes the code simpler and the interface more useful.

You can combine JavaScript date and time features with many other website patterns. Countdown logic works well with JavaScript countdown timers, date validation can improve JavaScript form validation, saved date preferences can be combined with JavaScript localStorage, and date-based calculators can support examples from our JavaScript calculator examples guide.

30 JavaScript Date and Time Examples

Now let’s look at 30 JavaScript date and time examples for real website projects. Each example uses a different time-related purpose, layout style, interface pattern, date calculation, formatting method, live update behavior, validation rule, deadline state, time zone display, or responsive UI approach, so you can build practical browser-based date and time features with visible JavaScript, HTML, and CSS code.

1. Live Digital Clock with Date and Weekday

A live digital clock is one of the most useful JavaScript date and time examples for dashboards, admin panels, booking systems, event pages, support widgets, and modern website headers. It shows the current time, date, and weekday in real time.

This example uses the JavaScript Date object and updates the interface every second with setInterval(). It also uses Intl.DateTimeFormat to create readable date and weekday labels.

Example 01

Live Digital Clock

This clock updates every second and displays the current time, weekday, and full date using JavaScript.

00:00:00 Loading weekday… Loading date…

JavaScript

(function () {
  const clock = document.querySelector("[data-vb-date-one]");
  if (!clock) return;

  const timeElement = clock.querySelector("[data-clock-time]");
  const weekdayElement = clock.querySelector("[data-clock-weekday]");
  const dateElement = clock.querySelector("[data-clock-date]");

  function updateClock() {
    const now = new Date();

    timeElement.textContent = new Intl.DateTimeFormat("en", {
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit"
    }).format(now);

    weekdayElement.textContent = new Intl.DateTimeFormat("en", {
      weekday: "long"
    }).format(now);

    dateElement.textContent = new Intl.DateTimeFormat("en", {
      month: "long",
      day: "numeric",
      year: "numeric"
    }).format(now);
  }

  updateClock();
  setInterval(updateClock, 1000);
})();

HTML

<div class="vb-date-one-demo">
  <div class="vb-date-one-card" data-vb-date-one>
    <div class="vb-date-one-orbit">
      <span></span>
      <span></span>
      <span></span>
    </div>

    <div class="vb-date-one-content">
      <span class="vb-date-one-kicker">Example 01</span>
      <h3>Live Digital Clock</h3>
      <p>This clock updates every second and displays the current time, weekday, and full date using JavaScript.</p>

      <div class="vb-date-one-clock">
        <strong data-clock-time>00:00:00</strong>
        <span data-clock-weekday>Loading weekday...</span>
        <small data-clock-date>Loading date...</small>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-one-demo,
.vb-date-one-demo * {
  box-sizing: border-box;
}

.vb-date-one-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 36px;
  background:
    radial-gradient(circle at 15% 18%, rgba(37, 99, 235, 0.22), transparent 34%),
    radial-gradient(circle at 88% 12%, rgba(249, 115, 22, 0.22), transparent 36%),
    linear-gradient(135deg, #eff6ff 0%, #fff7ed 58%, #ffffff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-date-one-card {
  position: relative;
  max-width: 980px;
  margin: 0 auto;
  min-height: 440px;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: 34px;
  background:
    radial-gradient(circle at center, rgba(59, 130, 246, 0.28), transparent 36%),
    linear-gradient(135deg, #0f172a 0%, #1e1b4b 50%, #7c2d12 100%);
  border: 1px solid rgba(255,255,255,0.12);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.32);
}

.vb-date-one-orbit {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.vb-date-one-orbit span {
  position: absolute;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.14);
  animation: vbDateOneFloat 8s ease-in-out infinite;
}

.vb-date-one-orbit span:nth-child(1) {
  width: 310px;
  height: 310px;
  left: -80px;
  top: -80px;
}

.vb-date-one-orbit span:nth-child(2) {
  width: 420px;
  height: 420px;
  right: -120px;
  bottom: -150px;
  animation-delay: -2s;
}

.vb-date-one-orbit span:nth-child(3) {
  width: 180px;
  height: 180px;
  right: 18%;
  top: 12%;
  animation-delay: -4s;
}

@keyframes vbDateOneFloat {
  0%, 100% {
    transform: translateY(0) scale(1);
    opacity: 0.55;
  }

  50% {
    transform: translateY(-16px) scale(1.04);
    opacity: 0.95;
  }
}

.vb-date-one-content {
  position: relative;
  z-index: 2;
  width: min(100%, 720px);
  padding: clamp(22px, 5vw, 54px);
  text-align: center;
}

.vb-date-one-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 13px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.vb-date-one-content h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(38px, 7vw, 78px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-one-content p {
  max-width: 580px;
  margin: 0 auto 26px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-one-clock {
  display: grid;
  gap: 10px;
  justify-items: center;
  max-width: 560px;
  margin: 0 auto;
  padding: clamp(18px, 4vw, 32px);
  border-radius: 28px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10);
  backdrop-filter: blur(16px);
}

.vb-date-one-clock strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(48px, 9vw, 96px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.06em;
  font-variant-numeric: tabular-nums;
}

.vb-date-one-clock span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: clamp(20px, 3vw, 30px);
  font-weight: 900;
}

.vb-date-one-clock small {
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 15px;
  line-height: 1.5;
  font-weight: 750;
}

@media (max-width: 560px) {
  .vb-date-one-card {
    min-height: 390px;
    border-radius: 26px;
  }

  .vb-date-one-clock {
    border-radius: 22px;
  }

  .vb-date-one-clock strong {
    font-size: 50px;
  }
}

This live JavaScript clock example is useful for admin dashboards, SaaS interfaces, booking systems, event websites, support pages, internal tools, and landing pages where real-time date and time information improves the user experience.

2. Date Formatter Tool with Multiple Output Styles

A JavaScript date formatter tool helps users convert a selected date into different readable formats. This is useful for dashboards, content tools, booking systems, CRM notes, invoices, event pages, and any interface where raw date input should become human-friendly text.

This example uses an HTML date input and formats the selected date into long date, short date, weekday, ISO-style date, and a custom friendly label. It uses Intl.DateTimeFormat for clean browser-based date formatting.

Example 02

Date Formatter Tool

Select a date and JavaScript will format it into several useful output styles.

Long date Choose a date
Short date Choose a date
Weekday Choose a date
ISO style Choose a date
Friendly label Choose a date to create a readable label.

JavaScript

(function () {
  const formatter = document.querySelector("[data-vb-date-two]");
  if (!formatter) return;

  const input = formatter.querySelector("[data-date-input]");
  const todayButton = formatter.querySelector("[data-today-button]");

  const outputLong = formatter.querySelector("[data-output-long]");
  const outputShort = formatter.querySelector("[data-output-short]");
  const outputWeekday = formatter.querySelector("[data-output-weekday]");
  const outputIso = formatter.querySelector("[data-output-iso]");
  const outputFriendly = formatter.querySelector("[data-output-friendly]");

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function formatDate() {
    if (!input.value) {
      outputLong.textContent = "Choose a date";
      outputShort.textContent = "Choose a date";
      outputWeekday.textContent = "Choose a date";
      outputIso.textContent = "Choose a date";
      outputFriendly.textContent = "Choose a date to create a readable label.";
      return;
    }

    const selectedDate = createLocalDate(input.value);

    outputLong.textContent = new Intl.DateTimeFormat("en", {
      month: "long",
      day: "numeric",
      year: "numeric"
    }).format(selectedDate);

    outputShort.textContent = new Intl.DateTimeFormat("en", {
      month: "short",
      day: "numeric",
      year: "numeric"
    }).format(selectedDate);

    outputWeekday.textContent = new Intl.DateTimeFormat("en", {
      weekday: "long"
    }).format(selectedDate);

    outputIso.textContent = input.value;

    outputFriendly.textContent =
      "Selected date is " +
      outputWeekday.textContent +
      ", " +
      outputLong.textContent +
      ".";
  }

  function setToday() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0");
    const day = String(today.getDate()).padStart(2, "0");

    input.value = year + "-" + month + "-" + day;
    formatDate();
  }

  input.addEventListener("change", formatDate);
  todayButton.addEventListener("click", setToday);

  setToday();
})();

HTML

<div class="vb-date-two-demo">
  <div class="vb-date-two-shell" data-vb-date-two>
    <div class="vb-date-two-panel">
      <span class="vb-date-two-kicker">Example 02</span>
      <h3>Date Formatter Tool</h3>
      <p>Select a date and JavaScript will format it into several useful output styles.</p>

      <label class="vb-date-two-input-wrap">
        <span>Choose a date</span>
        <input type="date" data-date-input>
      </label>

      <button type="button" data-today-button>Use Today</button>
    </div>

    <div class="vb-date-two-results">
      <div class="vb-date-two-result-card">
        <span>Long date</span>
        <strong data-output-long>Choose a date</strong>
      </div>

      <div class="vb-date-two-result-card">
        <span>Short date</span>
        <strong data-output-short>Choose a date</strong>
      </div>

      <div class="vb-date-two-result-card">
        <span>Weekday</span>
        <strong data-output-weekday>Choose a date</strong>
      </div>

      <div class="vb-date-two-result-card">
        <span>ISO style</span>
        <strong data-output-iso>Choose a date</strong>
      </div>

      <div class="vb-date-two-result-card vb-date-two-wide">
        <span>Friendly label</span>
        <strong data-output-friendly>Choose a date to create a readable label.</strong>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-two-demo,
.vb-date-two-demo * {
  box-sizing: border-box;
}

.vb-date-two-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 12% 16%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(236, 72, 153, 0.16), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #fdf2f8 52%, #eff6ff 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.09);
}

.vb-date-two-shell {
  display: grid;
  grid-template-columns: minmax(280px, 0.8fr) minmax(0, 1.2fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-two-panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 22px 70px rgba(15, 23, 42, 0.10);
}

.vb-date-two-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-two-panel h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-date-two-panel p {
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-two-input-wrap {
  display: grid;
  gap: 8px;
  margin-bottom: 14px;
}

.vb-date-two-input-wrap span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-two-input-wrap input {
  width: 100%;
  min-height: 52px;
  padding: 0 15px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-two-input-wrap input:focus {
  border-color: rgba(14, 165, 233, 0.8);
  box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.12);
}

.vb-date-two-panel button {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 12px 16px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #0ea5e9, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(14, 165, 233, 0.22);
}

.vb-date-two-results {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.vb-date-two-result-card {
  display: grid;
  gap: 10px;
  min-height: 150px;
  align-content: center;
  padding: 22px;
  border-radius: 26px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.90), rgba(255,255,255,0.70)),
    radial-gradient(circle at top right, rgba(14, 165, 233, 0.18), transparent 42%);
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.08);
}

.vb-date-two-result-card span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.10em;
}

.vb-date-two-result-card strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(20px, 3vw, 30px);
  line-height: 1.12;
  font-weight: 950;
  letter-spacing: -0.045em;
  word-break: break-word;
}

.vb-date-two-wide {
  grid-column: 1 / -1;
  min-height: 130px;
  background:
    radial-gradient(circle at 8% 20%, rgba(236, 72, 153, 0.16), transparent 34%),
    linear-gradient(135deg, #0f172a, #1e1b4b) !important;
}

.vb-date-two-wide span {
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
}

.vb-date-two-wide strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

@media (max-width: 880px) {
  .vb-date-two-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-date-two-results {
    grid-template-columns: 1fr;
  }

  .vb-date-two-panel,
  .vb-date-two-result-card {
    border-radius: 22px;
  }

  .vb-date-two-panel button {
    width: 100%;
  }
}

This JavaScript date formatter example is useful for admin tools, date input previews, booking forms, CMS interfaces, content scheduling tools, invoice dashboards, and any website feature where selected dates should become readable immediately.

3. Event Countdown Timer with Expired State

An event countdown timer is a practical JavaScript date and time component for webinars, product launches, limited offers, online events, course starts, booking deadlines, and landing pages. It shows how much time remains until a future date.

This example calculates the difference between the current time and a target event date. It updates every second and changes the interface when the countdown reaches zero, so the timer never shows confusing negative values.

Example 03

Event Countdown

JavaScript calculates the remaining time until the event date and updates the countdown every second.

00 Days
00 Hours
00 Minutes
00 Seconds

JavaScript

(function () {
  const countdown = document.querySelector("[data-vb-date-three]");
  if (!countdown) return;

  const eventDateValue = countdown.getAttribute("data-event-date");
  const eventDate = new Date(eventDateValue).getTime();

  const daysElement = countdown.querySelector("[data-days]");
  const hoursElement = countdown.querySelector("[data-hours]");
  const minutesElement = countdown.querySelector("[data-minutes]");
  const secondsElement = countdown.querySelector("[data-seconds]");
  const statusElement = countdown.querySelector("[data-event-status]");

  function padNumber(number) {
    return String(number).padStart(2, "0");
  }

  function updateCountdown() {
    const now = new Date().getTime();
    const distance = eventDate - now;

    if (distance <= 0) {
      daysElement.textContent = "00";
      hoursElement.textContent = "00";
      minutesElement.textContent = "00";
      secondsElement.textContent = "00";
      statusElement.textContent = "Event has started";
      countdown.classList.add("is-expired");
      return;
    }

    const days = Math.floor(distance / (1000 * 60 * 60 * 24));
    const hours = Math.floor((distance / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((distance / (1000 * 60)) % 60);
    const seconds = Math.floor((distance / 1000) % 60);

    daysElement.textContent = padNumber(days);
    hoursElement.textContent = padNumber(hours);
    minutesElement.textContent = padNumber(minutes);
    secondsElement.textContent = padNumber(seconds);
    statusElement.textContent = "Countdown is active";
    countdown.classList.remove("is-expired");
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-date-three-demo">
  <div class="vb-date-three-card" data-vb-date-three data-event-date="2026-12-31T23:59:59">
    <div class="vb-date-three-top">
      <span class="vb-date-three-kicker">Example 03</span>
      <h3>Event Countdown</h3>
      <p>JavaScript calculates the remaining time until the event date and updates the countdown every second.</p>
    </div>

    <div class="vb-date-three-countdown" data-countdown-wrap>
      <div class="vb-date-three-time-box">
        <strong data-days>00</strong>
        <span>Days</span>
      </div>

      <div class="vb-date-three-time-box">
        <strong data-hours>00</strong>
        <span>Hours</span>
      </div>

      <div class="vb-date-three-time-box">
        <strong data-minutes>00</strong>
        <span>Minutes</span>
      </div>

      <div class="vb-date-three-time-box">
        <strong data-seconds>00</strong>
        <span>Seconds</span>
      </div>
    </div>

    <div class="vb-date-three-footer">
      <span data-event-label>Event date: December 31, 2026</span>
      <strong data-event-status>Countdown is active</strong>
    </div>
  </div>
</div>

CSS

.vb-date-three-demo,
.vb-date-three-demo * {
  box-sizing: border-box;
}

.vb-date-three-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 18% 16%, rgba(248, 113, 113, 0.18), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(251, 191, 36, 0.18), transparent 34%),
    linear-gradient(135deg, #fff7ed 0%, #fef2f2 52%, #ffffff 100%) !important;
  border: 1px solid rgba(248, 113, 113, 0.18);
  box-shadow: 0 28px 80px rgba(127, 29, 29, 0.08);
}

.vb-date-three-card {
  max-width: 1080px;
  margin: 0 auto;
  padding: clamp(22px, 5vw, 46px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 14%, rgba(251, 146, 60, 0.22), transparent 34%),
    radial-gradient(circle at 92% 18%, rgba(244, 63, 94, 0.20), transparent 34%),
    linear-gradient(135deg, #111827 0%, #7f1d1d 55%, #9a3412 100%);
  border: 1px solid rgba(255,255,255,0.13);
  box-shadow: 0 30px 90px rgba(127, 29, 29, 0.28);
  overflow: hidden;
}

.vb-date-three-top {
  display: grid;
  gap: 13px;
  max-width: 760px;
  margin-bottom: 28px;
}

.vb-date-three-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-three-top h3 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(38px, 7vw, 76px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-three-top p {
  max-width: 640px;
  margin: 0 !important;
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-three-countdown {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 22px;
}

.vb-date-three-time-box {
  display: grid;
  gap: 9px;
  justify-items: center;
  min-height: 165px;
  align-content: center;
  padding: 18px;
  border-radius: 26px;
  background: rgba(255,255,255,0.11);
  border: 1px solid rgba(255,255,255,0.16);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10);
  backdrop-filter: blur(12px);
}

.vb-date-three-time-box strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(42px, 7vw, 74px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.065em;
  font-variant-numeric: tabular-nums;
}

.vb-date-three-time-box span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-three-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  padding: 16px 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-three-footer span {
  color: #ffedd5 !important;
  -webkit-text-fill-color: #ffedd5 !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-date-three-footer strong {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: rgba(34, 197, 94, 0.18);
  border: 1px solid rgba(74, 222, 128, 0.28);
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
}

.vb-date-three-card.is-expired .vb-date-three-footer strong {
  background: rgba(248, 113, 113, 0.20);
  border-color: rgba(248, 113, 113, 0.34);
  color: #fecaca !important;
  -webkit-text-fill-color: #fecaca !important;
}

@media (max-width: 760px) {
  .vb-date-three-countdown {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-date-three-time-box {
    min-height: 140px;
  }
}

@media (max-width: 480px) {
  .vb-date-three-countdown {
    grid-template-columns: 1fr;
  }

  .vb-date-three-card {
    border-radius: 26px;
  }

  .vb-date-three-time-box {
    min-height: 120px;
    border-radius: 22px;
  }
}

This JavaScript event countdown example is useful for webinar landing pages, product launches, event websites, ecommerce sale pages, online course registrations, booking deadlines, and limited-time promotional sections.

4. Time Zone Converter Cards

A JavaScript time zone converter is useful for international websites, remote teams, online meetings, support pages, event platforms, and booking interfaces. It helps visitors compare the same selected time across different regions.

This example lets the user choose a meeting date and time, then JavaScript displays the matching time in New York, London, Tallinn, Tokyo, and Sydney. It uses Intl.DateTimeFormat with different timeZone values.

Example 04

Time Zone Converter

Select one meeting time and instantly compare it across major time zones.

New York --:-- America/New York
London --:-- Europe/London
Tallinn --:-- Europe/Tallinn
Tokyo --:-- Asia/Tokyo
Sydney --:-- Australia/Sydney

JavaScript

(function () {
  const converter = document.querySelector("[data-vb-date-four]");
  if (!converter) return;

  const input = converter.querySelector("[data-timezone-input]");
  const nowButton = converter.querySelector("[data-timezone-now]");
  const outputs = converter.querySelectorAll("[data-zone-output]");

  function getLocalDateTimeValue(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    const hours = String(date.getHours()).padStart(2, "0");
    const minutes = String(date.getMinutes()).padStart(2, "0");

    return year + "-" + month + "-" + day + "T" + hours + ":" + minutes;
  }

  function updateZones() {
    if (!input.value) return;

    const selectedDate = new Date(input.value);

    outputs.forEach(function (output) {
      const zone = output.getAttribute("data-zone-output");

      output.textContent = new Intl.DateTimeFormat("en", {
        timeZone: zone,
        weekday: "short",
        month: "short",
        day: "numeric",
        hour: "2-digit",
        minute: "2-digit"
      }).format(selectedDate);
    });
  }

  function useCurrentTime() {
    input.value = getLocalDateTimeValue(new Date());
    updateZones();
  }

  input.addEventListener("change", updateZones);
  nowButton.addEventListener("click", useCurrentTime);

  useCurrentTime();
})();

HTML

<div class="vb-date-four-demo">
  <div class="vb-date-four-shell" data-vb-date-four>
    <div class="vb-date-four-intro">
      <span class="vb-date-four-kicker">Example 04</span>
      <h3>Time Zone Converter</h3>
      <p>Select one meeting time and instantly compare it across major time zones.</p>

      <label>
        <span>Meeting date and time</span>
        <input type="datetime-local" data-timezone-input>
      </label>

      <button type="button" data-timezone-now>Use Current Time</button>
    </div>

    <div class="vb-date-four-grid">
      <article class="vb-date-four-zone">
        <span>New York</span>
        <strong data-zone-output="America/New_York">--:--</strong>
        <small>America/New York</small>
      </article>

      <article class="vb-date-four-zone">
        <span>London</span>
        <strong data-zone-output="Europe/London">--:--</strong>
        <small>Europe/London</small>
      </article>

      <article class="vb-date-four-zone">
        <span>Tallinn</span>
        <strong data-zone-output="Europe/Tallinn">--:--</strong>
        <small>Europe/Tallinn</small>
      </article>

      <article class="vb-date-four-zone">
        <span>Tokyo</span>
        <strong data-zone-output="Asia/Tokyo">--:--</strong>
        <small>Asia/Tokyo</small>
      </article>

      <article class="vb-date-four-zone vb-date-four-wide">
        <span>Sydney</span>
        <strong data-zone-output="Australia/Sydney">--:--</strong>
        <small>Australia/Sydney</small>
      </article>
    </div>
  </div>
</div>

CSS

.vb-date-four-demo,
.vb-date-four-demo * {
  box-sizing: border-box;
}

.vb-date-four-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 14% 16%, rgba(37, 99, 235, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(20, 184, 166, 0.18), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfeff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.09);
}

.vb-date-four-shell {
  display: grid;
  grid-template-columns: minmax(280px, 0.85fr) minmax(0, 1.15fr);
  gap: 20px;
  max-width: 1140px;
  margin: 0 auto;
}

.vb-date-four-intro {
  min-width: 0;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 30px;
  background:
    radial-gradient(circle at top left, rgba(96, 165, 250, 0.20), transparent 38%),
    linear-gradient(135deg, #0f172a 0%, #164e63 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.22);
}

.vb-date-four-kicker {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-four-intro h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-four-intro p {
  margin: 0 0 22px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-four-intro label {
  display: grid;
  gap: 8px;
  margin-bottom: 14px;
}

.vb-date-four-intro label span {
  color: #cffafe !important;
  -webkit-text-fill-color: #cffafe !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-four-intro input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(255,255,255,0.20);
  border-radius: 17px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-four-intro input::-webkit-calendar-picker-indicator {
  filter: invert(1);
  opacity: 0.9;
}

.vb-date-four-intro input:focus {
  border-color: rgba(103, 232, 249, 0.85);
  box-shadow: 0 0 0 4px rgba(103, 232, 249, 0.14);
}

.vb-date-four-intro button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 12px 16px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #06b6d4, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(6, 182, 212, 0.24);
}

.vb-date-four-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
}

.vb-date-four-zone {
  display: grid;
  align-content: center;
  gap: 10px;
  min-width: 0;
  min-height: 168px;
  padding: 22px;
  border-radius: 26px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.08);
}

.vb-date-four-zone span {
  color: #0891b2 !important;
  -webkit-text-fill-color: #0891b2 !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-date-four-zone strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(24px, 4vw, 42px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-date-four-zone small {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.35;
  font-weight: 750;
  overflow-wrap: anywhere;
}

.vb-date-four-wide {
  grid-column: 1 / -1;
  min-height: 138px;
  background:
    radial-gradient(circle at 10% 20%, rgba(20, 184, 166, 0.14), transparent 34%),
    linear-gradient(135deg, #f8fafc, #ecfeff) !important;
}

@media (max-width: 900px) {
  .vb-date-four-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 580px) {
  .vb-date-four-grid {
    grid-template-columns: 1fr;
  }

  .vb-date-four-intro,
  .vb-date-four-zone {
    border-radius: 22px;
  }

  .vb-date-four-intro button {
    width: 100%;
  }

  .vb-date-four-zone {
    min-height: 130px;
  }
}

This JavaScript time zone converter example is useful for remote team pages, event registration pages, international booking forms, webinar landing pages, SaaS dashboards, and support pages that serve users in different countries.

5. Working Hours Open or Closed Checker

A working hours checker is a practical JavaScript date and time example for business websites, support widgets, restaurants, clinics, agencies, ecommerce stores, and local service pages. It tells visitors whether the business is currently open or closed.

This example checks the current day and hour in JavaScript. It shows an open or closed status, displays today’s working hours, and explains when the business is available. The layout is responsive and works well inside WordPress content blocks.

Example 05

Business Hours Checker

JavaScript checks the current day and time, then displays whether the business is open or closed.

Checking... Please wait while JavaScript checks the current time.
Today Loading...
Mon09:00 - 17:00
Tue09:00 - 17:00
Wed09:00 - 17:00
Thu09:00 - 17:00
Fri09:00 - 16:00
SatClosed
SunClosed

JavaScript

(function () {
  const checker = document.querySelector("[data-vb-date-five]");
  if (!checker) return;

  const statusBox = checker.querySelector("[data-status-box]");
  const statusText = checker.querySelector("[data-open-status]");
  const messageText = checker.querySelector("[data-open-message]");
  const todayHours = checker.querySelector("[data-today-hours]");
  const dayRows = checker.querySelectorAll("[data-day-row]");

  const schedule = {
    0: null,
    1: { open: 9, close: 17, label: "09:00 - 17:00" },
    2: { open: 9, close: 17, label: "09:00 - 17:00" },
    3: { open: 9, close: 17, label: "09:00 - 17:00" },
    4: { open: 9, close: 17, label: "09:00 - 17:00" },
    5: { open: 9, close: 16, label: "09:00 - 16:00" },
    6: null
  };

  function checkBusinessHours() {
    const now = new Date();
    const day = now.getDay();
    const hour = now.getHours();
    const todaySchedule = schedule[day];

    dayRows.forEach(function (row) {
      row.classList.toggle("is-today", Number(row.getAttribute("data-day-row")) === day);
    });

    todayHours.textContent = todaySchedule ? todaySchedule.label : "Closed";

    statusBox.classList.remove("is-open", "is-closed");

    if (todaySchedule && hour >= todaySchedule.open && hour < todaySchedule.close) {
      statusBox.classList.add("is-open");
      statusText.textContent = "We are open now";
      messageText.textContent = "Today’s working hours are " + todaySchedule.label + ". You can contact us during business hours.";
    } else {
      statusBox.classList.add("is-closed");
      statusText.textContent = "We are closed now";

      if (todaySchedule) {
        messageText.textContent = "Today’s working hours are " + todaySchedule.label + ". Please contact us during the next open period.";
      } else {
        messageText.textContent = "The business is closed today. Please check the weekly schedule for the next open day.";
      }
    }
  }

  checkBusinessHours();
})();

HTML

<div class="vb-date-five-demo">
  <div class="vb-date-five-card" data-vb-date-five>
    <div class="vb-date-five-main">
      <span class="vb-date-five-kicker">Example 05</span>
      <h3>Business Hours Checker</h3>
      <p>JavaScript checks the current day and time, then displays whether the business is open or closed.</p>

      <div class="vb-date-five-status" data-status-box>
        <strong data-open-status>Checking...</strong>
        <span data-open-message>Please wait while JavaScript checks the current time.</span>
      </div>
    </div>

    <div class="vb-date-five-side">
      <div class="vb-date-five-today">
        <span>Today</span>
        <strong data-today-hours>Loading...</strong>
      </div>

      <div class="vb-date-five-schedule">
        <div data-day-row="1"><span>Mon</span><strong>09:00 - 17:00</strong></div>
        <div data-day-row="2"><span>Tue</span><strong>09:00 - 17:00</strong></div>
        <div data-day-row="3"><span>Wed</span><strong>09:00 - 17:00</strong></div>
        <div data-day-row="4"><span>Thu</span><strong>09:00 - 17:00</strong></div>
        <div data-day-row="5"><span>Fri</span><strong>09:00 - 16:00</strong></div>
        <div data-day-row="6"><span>Sat</span><strong>Closed</strong></div>
        <div data-day-row="0"><span>Sun</span><strong>Closed</strong></div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-five-demo,
.vb-date-five-demo * {
  box-sizing: border-box;
}

.vb-date-five-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 38px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 197, 94, 0.18), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-five-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 380px);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 26px 80px rgba(15, 23, 42, 0.10);
}

.vb-date-five-main {
  display: grid;
  align-content: center;
  min-width: 0;
  padding: clamp(4px, 2vw, 14px);
}

.vb-date-five-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-five-main h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 68px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-five-main p {
  max-width: 620px;
  margin: 0 0 24px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-five-status {
  display: grid;
  gap: 8px;
  max-width: 620px;
  padding: 20px;
  border-radius: 24px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-date-five-status strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(26px, 4vw, 42px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.055em;
}

.vb-date-five-status span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 15px;
  line-height: 1.55;
  font-weight: 750;
}

.vb-date-five-status.is-open {
  background: #ecfdf5;
  border-color: rgba(34, 197, 94, 0.28);
}

.vb-date-five-status.is-open strong {
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
}

.vb-date-five-status.is-closed {
  background: #fef2f2;
  border-color: rgba(248, 113, 113, 0.30);
}

.vb-date-five-status.is-closed strong {
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-date-five-side {
  display: grid;
  gap: 14px;
  min-width: 0;
}

.vb-date-five-today {
  display: grid;
  gap: 8px;
  padding: 20px;
  border-radius: 24px;
  background:
    radial-gradient(circle at top right, rgba(34, 197, 94, 0.18), transparent 42%),
    linear-gradient(135deg, #0f172a, #14532d) !important;
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-date-five-today span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.vb-date-five-today strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(24px, 4vw, 38px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.055em;
}

.vb-date-five-schedule {
  display: grid;
  gap: 8px;
}

.vb-date-five-schedule div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-width: 0;
  padding: 12px 14px;
  border-radius: 16px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.18);
}

.vb-date-five-schedule div.is-today {
  background: #eff6ff;
  border-color: rgba(37, 99, 235, 0.28);
}

.vb-date-five-schedule span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-date-five-schedule strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 13px;
  font-weight: 850;
  text-align: right;
}

@media (max-width: 900px) {
  .vb-date-five-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-date-five-card,
  .vb-date-five-status,
  .vb-date-five-today {
    border-radius: 22px;
  }

  .vb-date-five-schedule div {
    align-items: flex-start;
    flex-direction: column;
    gap: 5px;
  }

  .vb-date-five-schedule strong {
    text-align: left;
  }
}

This JavaScript working hours checker is useful for local business websites, contact pages, live chat widgets, support teams, restaurants, clinics, agencies, service companies, and ecommerce stores that want to show availability clearly.

6. Delivery Date Calculator with Business Days

A delivery date calculator is a highly practical JavaScript date and time example for ecommerce websites, quote forms, shipping pages, product pages, service bookings, and order dashboards. It helps users understand when they can expect delivery or completion.

This example adds business days to the selected order date and skips weekends. The user can choose a processing speed, and JavaScript calculates the estimated delivery date with a clean responsive result card.

Example 06

Delivery Date Calculator

Select an order date and delivery speed. JavaScript calculates the estimated delivery date while skipping weekends.

Estimated result
Weekday Select order date

Your estimated delivery date will appear here.

Order --
Business days --
Weekend rule Skipped

JavaScript

(function () {
  const calculator = document.querySelector("[data-vb-date-six]");
  if (!calculator) return;

  const orderInput = calculator.querySelector("[data-order-date]");
  const speedSelect = calculator.querySelector("[data-delivery-speed]");
  const calculateButton = calculator.querySelector("[data-calculate-delivery]");

  const resultWeekday = calculator.querySelector("[data-result-weekday]");
  const resultDate = calculator.querySelector("[data-result-date]");
  const resultMessage = calculator.querySelector("[data-result-message]");
  const stepOrder = calculator.querySelector("[data-step-order]");
  const stepDays = calculator.querySelector("[data-step-days]");

  function formatInputDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");

    return year + "-" + month + "-" + day;
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function addBusinessDays(startDate, businessDays) {
    const result = new Date(startDate);
    let addedDays = 0;

    while (addedDays < businessDays) {
      result.setDate(result.getDate() + 1);

      const day = result.getDay();
      const isWeekend = day === 0 || day === 6;

      if (!isWeekend) {
        addedDays += 1;
      }
    }

    return result;
  }

  function formatLongDate(date) {
    return new Intl.DateTimeFormat("en", {
      month: "long",
      day: "numeric",
      year: "numeric"
    }).format(date);
  }

  function calculateDeliveryDate() {
    if (!orderInput.value) {
      resultWeekday.textContent = "Missing order date";
      resultDate.textContent = "Select order date";
      resultMessage.textContent = "Please select an order date before calculating the estimated delivery date.";
      stepOrder.textContent = "--";
      stepDays.textContent = "--";
      return;
    }

    const orderDate = createLocalDate(orderInput.value);
    const businessDays = Number(speedSelect.value);
    const deliveryDate = addBusinessDays(orderDate, businessDays);

    resultWeekday.textContent = new Intl.DateTimeFormat("en", {
      weekday: "long"
    }).format(deliveryDate);

    resultDate.textContent = formatLongDate(deliveryDate);
    resultMessage.textContent = "Estimated delivery is " + businessDays + " business days after the selected order date. Weekends are skipped automatically.";

    stepOrder.textContent = formatLongDate(orderDate);
    stepDays.textContent = businessDays + " days";
  }

  function setToday() {
    orderInput.value = formatInputDate(new Date());
    calculateDeliveryDate();
  }

  calculateButton.addEventListener("click", calculateDeliveryDate);
  speedSelect.addEventListener("change", calculateDeliveryDate);
  orderInput.addEventListener("change", calculateDeliveryDate);

  setToday();
})();

HTML

<div class="vb-date-six-demo">
  <div class="vb-date-six-shell" data-vb-date-six>
    <div class="vb-date-six-form-card">
      <span class="vb-date-six-kicker">Example 06</span>
      <h3>Delivery Date Calculator</h3>
      <p>Select an order date and delivery speed. JavaScript calculates the estimated delivery date while skipping weekends.</p>

      <div class="vb-date-six-controls">
        <label>
          <span>Order date</span>
          <input type="date" data-order-date>
        </label>

        <label>
          <span>Delivery speed</span>
          <select data-delivery-speed>
            <option value="7">Standard delivery - 7 business days</option>
            <option value="4">Express delivery - 4 business days</option>
            <option value="2">Priority delivery - 2 business days</option>
          </select>
        </label>
      </div>

      <button type="button" data-calculate-delivery>Calculate Delivery Date</button>
    </div>

    <div class="vb-date-six-result-card">
      <div class="vb-date-six-badge">Estimated result</div>

      <div class="vb-date-six-date">
        <span data-result-weekday>Weekday</span>
        <strong data-result-date>Select order date</strong>
      </div>

      <p data-result-message>Your estimated delivery date will appear here.</p>

      <div class="vb-date-six-steps">
        <div>
          <span>Order</span>
          <strong data-step-order>--</strong>
        </div>
        <div>
          <span>Business days</span>
          <strong data-step-days>--</strong>
        </div>
        <div>
          <span>Weekend rule</span>
          <strong>Skipped</strong>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-six-demo,
.vb-date-six-demo * {
  box-sizing: border-box;
}

.vb-date-six-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 15% 18%, rgba(124, 58, 237, 0.18), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(245, 158, 11, 0.18), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #fffbeb 55%, #ffffff 100%) !important;
  border: 1px solid rgba(124, 58, 237, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.09);
}

.vb-date-six-shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 430px);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-six-form-card,
.vb-date-six-result-card {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-six-form-card {
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-six-kicker {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-six-form-card h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-six-form-card p {
  max-width: 640px;
  margin: 0 0 24px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-six-controls {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 16px;
}

.vb-date-six-controls label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-six-controls label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-six-controls input,
.vb-date-six-controls select {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px;
  font-weight: 750;
  outline: none;
}

.vb-date-six-controls input:focus,
.vb-date-six-controls select:focus {
  border-color: rgba(124, 58, 237, 0.78);
  box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.12);
}

.vb-date-six-form-card button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #7c3aed, #f97316);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(124, 58, 237, 0.24);
}

.vb-date-six-result-card {
  display: grid;
  align-content: center;
  gap: 18px;
  padding: clamp(22px, 4vw, 34px);
  background:
    radial-gradient(circle at 18% 14%, rgba(253, 186, 116, 0.20), transparent 34%),
    linear-gradient(135deg, #111827 0%, #312e81 52%, #7c2d12 100%) !important;
  overflow: hidden;
}

.vb-date-six-badge {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.11);
  border: 1px solid rgba(255,255,255,0.16);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-six-date {
  display: grid;
  gap: 8px;
}

.vb-date-six-date span {
  color: #c4b5fd !important;
  -webkit-text-fill-color: #c4b5fd !important;
  font-size: 15px;
  font-weight: 900;
}

.vb-date-six-date strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 58px);
  line-height: 0.98;
  font-weight: 950;
  letter-spacing: -0.065em;
  overflow-wrap: anywhere;
}

.vb-date-six-result-card p {
  margin: 0 !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-date-six-steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

.vb-date-six-steps div {
  min-width: 0;
  padding: 13px;
  border-radius: 17px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-date-six-steps span {
  display: block;
  margin-bottom: 5px;
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 11px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-date-six-steps strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  line-height: 1.25;
  font-weight: 850;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-date-six-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-date-six-controls,
  .vb-date-six-steps {
    grid-template-columns: 1fr;
  }

  .vb-date-six-form-card,
  .vb-date-six-result-card {
    border-radius: 24px;
  }

  .vb-date-six-form-card button {
    width: 100%;
  }
}

This JavaScript delivery date calculator is useful for ecommerce product pages, shipping estimate widgets, quote request forms, service booking systems, production timelines, order dashboards, and checkout helper sections.

Need a custom JavaScript feature?

Can’t build this yourself? We can create it for your website.

If you need a custom calculator, booking tool, countdown, date picker, dashboard widget, or interactive JavaScript component, our team can build a responsive and professional solution for your website.

Contact Us

7. Age Calculator from Birth Date

A JavaScript age calculator is a practical date and time example for signup forms, health calculators, education websites, membership platforms, booking forms, insurance pages, and profile dashboards. It calculates a person’s age from a selected birth date.

This example calculates years, months, and days from the selected date of birth. It also shows the next birthday countdown and prevents future birth dates from being used.

Example 07

Age Calculator

Select a birth date and JavaScript calculates the exact age and next birthday countdown.

Your age -- Select a birth date to calculate age.
Years --
Months --
Days --
Next birthday --

JavaScript

(function () {
  const calculator = document.querySelector("[data-vb-date-seven]");
  if (!calculator) return;

  const input = calculator.querySelector("[data-birth-date]");
  const button = calculator.querySelector("[data-age-calculate]");
  const error = calculator.querySelector("[data-age-error]");

  const ageMain = calculator.querySelector("[data-age-main]");
  const ageDetail = calculator.querySelector("[data-age-detail]");
  const yearsOutput = calculator.querySelector("[data-age-years]");
  const monthsOutput = calculator.querySelector("[data-age-months]");
  const daysOutput = calculator.querySelector("[data-age-days]");
  const birthdayOutput = calculator.querySelector("[data-birthday-countdown]");

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function calculateAge() {
    error.textContent = "";

    if (!input.value) {
      error.textContent = "Please select a birth date.";
      return;
    }

    const birthDate = createLocalDate(input.value);
    const today = new Date();

    birthDate.setHours(0, 0, 0, 0);
    today.setHours(0, 0, 0, 0);

    if (birthDate > today) {
      error.textContent = "Birth date cannot be in the future.";
      ageMain.textContent = "--";
      ageDetail.textContent = "Select a valid birth date.";
      yearsOutput.textContent = "--";
      monthsOutput.textContent = "--";
      daysOutput.textContent = "--";
      birthdayOutput.textContent = "--";
      return;
    }

    let years = today.getFullYear() - birthDate.getFullYear();
    let months = today.getMonth() - birthDate.getMonth();
    let days = today.getDate() - birthDate.getDate();

    if (days < 0) {
      months -= 1;
      const previousMonth = new Date(today.getFullYear(), today.getMonth(), 0);
      days += previousMonth.getDate();
    }

    if (months < 0) {
      years -= 1;
      months += 12;
    }

    const nextBirthday = new Date(today.getFullYear(), birthDate.getMonth(), birthDate.getDate());

    if (nextBirthday < today) {
      nextBirthday.setFullYear(today.getFullYear() + 1);
    }

    const birthdayDiff = nextBirthday - today;
    const birthdayDays = Math.ceil(birthdayDiff / (1000 * 60 * 60 * 24));

    ageMain.textContent = years + " years";
    ageDetail.textContent = "You are " + years + " years, " + months + " months, and " + days + " days old.";
    yearsOutput.textContent = years;
    monthsOutput.textContent = months;
    daysOutput.textContent = days;
    birthdayOutput.textContent = birthdayDays === 0 ? "Today" : birthdayDays + " days";
  }

  button.addEventListener("click", calculateAge);
  input.addEventListener("change", calculateAge);
})();

HTML

<div class="vb-date-seven-demo">
  <div class="vb-date-seven-shell" data-vb-date-seven>
    <div class="vb-date-seven-form">
      <span class="vb-date-seven-kicker">Example 07</span>
      <h3>Age Calculator</h3>
      <p>Select a birth date and JavaScript calculates the exact age and next birthday countdown.</p>

      <label>
        <span>Date of birth</span>
        <input type="date" data-birth-date>
      </label>

      <button type="button" data-age-calculate>Calculate Age</button>
      <small data-age-error></small>
    </div>

    <div class="vb-date-seven-results">
      <div class="vb-date-seven-age-card">
        <span>Your age</span>
        <strong data-age-main>--</strong>
        <small data-age-detail>Select a birth date to calculate age.</small>
      </div>

      <div class="vb-date-seven-mini-grid">
        <div>
          <span>Years</span>
          <strong data-age-years>--</strong>
        </div>
        <div>
          <span>Months</span>
          <strong data-age-months>--</strong>
        </div>
        <div>
          <span>Days</span>
          <strong data-age-days>--</strong>
        </div>
      </div>

      <div class="vb-date-seven-birthday">
        <span>Next birthday</span>
        <strong data-birthday-countdown>--</strong>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-seven-demo,
.vb-date-seven-demo * {
  box-sizing: border-box;
}

.vb-date-seven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 16%, rgba(236, 72, 153, 0.16), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(99, 102, 241, 0.18), transparent 34%),
    linear-gradient(135deg, #fdf2f8 0%, #eef2ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(236, 72, 153, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-seven-shell {
  display: grid;
  grid-template-columns: minmax(280px, 0.82fr) minmax(0, 1.18fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-seven-form,
.vb-date-seven-results {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-seven-form {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 36px);
  background: #ffffff;
}

.vb-date-seven-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #fce7f3;
  color: #be185d !important;
  -webkit-text-fill-color: #be185d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-seven-form h3 {
  margin: 0 0 14px !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-seven-form p {
  margin: 0 0 22px !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-seven-form label {
  display: grid;
  gap: 8px;
  margin-bottom: 14px;
}

.vb-date-seven-form label span {
  color: #374151 !important;
  -webkit-text-fill-color: #374151 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-seven-form input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f9fafb;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-seven-form input:focus {
  border-color: rgba(236, 72, 153, 0.78);
  box-shadow: 0 0 0 4px rgba(236, 72, 153, 0.12);
}

.vb-date-seven-form button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #db2777, #4f46e5);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(219, 39, 119, 0.22);
}

.vb-date-seven-form small {
  display: block;
  min-height: 20px;
  margin-top: 10px;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 13px;
  line-height: 1.4;
  font-weight: 750;
}

.vb-date-seven-results {
  display: grid;
  gap: 14px;
  padding: clamp(20px, 4vw, 30px);
  background:
    radial-gradient(circle at 12% 16%, rgba(236, 72, 153, 0.18), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(99, 102, 241, 0.24), transparent 34%),
    linear-gradient(135deg, #111827 0%, #312e81 52%, #831843 100%) !important;
}

.vb-date-seven-age-card,
.vb-date-seven-birthday {
  min-width: 0;
  padding: 22px;
  border-radius: 26px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-seven-age-card {
  display: grid;
  gap: 9px;
}

.vb-date-seven-age-card span,
.vb-date-seven-birthday span,
.vb-date-seven-mini-grid span {
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-seven-age-card strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(40px, 7vw, 76px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.075em;
}

.vb-date-seven-age-card small {
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 15px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-date-seven-mini-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-seven-mini-grid div {
  min-width: 0;
  display: grid;
  gap: 7px;
  padding: 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-date-seven-mini-grid strong,
.vb-date-seven-birthday strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(22px, 4vw, 34px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.05em;
  overflow-wrap: anywhere;
}

.vb-date-seven-birthday {
  display: grid;
  gap: 8px;
}

@media (max-width: 900px) {
  .vb-date-seven-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-seven-mini-grid {
    grid-template-columns: 1fr;
  }

  .vb-date-seven-form,
  .vb-date-seven-results,
  .vb-date-seven-age-card,
  .vb-date-seven-birthday {
    border-radius: 24px;
  }

  .vb-date-seven-form button {
    width: 100%;
  }
}

This JavaScript age calculator example is useful for signup forms, profile pages, membership websites, school forms, health calculators, booking systems, insurance forms, and any feature where age must be calculated from a birth date.

8. Date Difference Calculator Between Two Dates

A date difference calculator is useful for project dashboards, rental forms, booking websites, event planning tools, travel pages, service estimates, subscription dashboards, and deadline tracking. It calculates how much time exists between two selected dates.

This example lets users choose a start date and an end date. JavaScript calculates the total number of days, weeks, and approximate months between them, then shows a clear summary card.

Example 08

Date Difference Calculator

Select two dates and calculate the duration between them in days, weeks, and approximate months.

Total duration -- days Select a start and end date.
Weeks --
Months --
Range --

JavaScript

(function () {
  const calculator = document.querySelector("[data-vb-date-eight]");
  if (!calculator) return;

  const startInput = calculator.querySelector("[data-start-date]");
  const endInput = calculator.querySelector("[data-end-date]");
  const button = calculator.querySelector("[data-difference-calculate]");
  const error = calculator.querySelector("[data-difference-error]");

  const daysOutput = calculator.querySelector("[data-total-days]");
  const weeksOutput = calculator.querySelector("[data-total-weeks]");
  const monthsOutput = calculator.querySelector("[data-total-months]");
  const rangeOutput = calculator.querySelector("[data-range-type]");
  const labelOutput = calculator.querySelector("[data-duration-label]");

  function formatInputDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");

    return year + "-" + month + "-" + day;
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function calculateDifference() {
    error.textContent = "";

    if (!startInput.value || !endInput.value) {
      error.textContent = "Please select both dates.";
      return;
    }

    const startDate = createLocalDate(startInput.value);
    const endDate = createLocalDate(endInput.value);

    if (endDate < startDate) {
      error.textContent = "End date must be after the start date.";
      daysOutput.textContent = "-- days";
      weeksOutput.textContent = "--";
      monthsOutput.textContent = "--";
      rangeOutput.textContent = "--";
      labelOutput.textContent = "Please fix the selected date range.";
      return;
    }

    const differenceMs = endDate - startDate;
    const totalDays = Math.round(differenceMs / (1000 * 60 * 60 * 24));
    const weeks = Math.floor(totalDays / 7);
    const months = (totalDays / 30.44).toFixed(1);

    daysOutput.textContent = totalDays + " days";
    weeksOutput.textContent = weeks + " weeks";
    monthsOutput.textContent = months;
    rangeOutput.textContent = totalDays === 0 ? "Same day" : "Date range";
    labelOutput.textContent = "The selected range is " + totalDays + " day" + (totalDays === 1 ? "" : "s") + " long.";
  }

  function setDefaultDates() {
    const today = new Date();
    const nextWeek = new Date();
    nextWeek.setDate(today.getDate() + 7);

    startInput.value = formatInputDate(today);
    endInput.value = formatInputDate(nextWeek);
    calculateDifference();
  }

  button.addEventListener("click", calculateDifference);
  startInput.addEventListener("change", calculateDifference);
  endInput.addEventListener("change", calculateDifference);

  setDefaultDates();
})();

HTML

<div class="vb-date-eight-demo">
  <div class="vb-date-eight-card" data-vb-date-eight>
    <div class="vb-date-eight-header">
      <span class="vb-date-eight-kicker">Example 08</span>
      <h3>Date Difference Calculator</h3>
      <p>Select two dates and calculate the duration between them in days, weeks, and approximate months.</p>
    </div>

    <div class="vb-date-eight-body">
      <div class="vb-date-eight-inputs">
        <label>
          <span>Start date</span>
          <input type="date" data-start-date>
        </label>

        <label>
          <span>End date</span>
          <input type="date" data-end-date>
        </label>

        <button type="button" data-difference-calculate>Calculate Difference</button>

        <small data-difference-error></small>
      </div>

      <div class="vb-date-eight-results">
        <div class="vb-date-eight-main-result">
          <span>Total duration</span>
          <strong data-total-days>-- days</strong>
          <small data-duration-label>Select a start and end date.</small>
        </div>

        <div class="vb-date-eight-metrics">
          <div>
            <span>Weeks</span>
            <strong data-total-weeks>--</strong>
          </div>
          <div>
            <span>Months</span>
            <strong data-total-months>--</strong>
          </div>
          <div>
            <span>Range</span>
            <strong data-range-type>--</strong>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-eight-demo,
.vb-date-eight-demo * {
  box-sizing: border-box;
}

.vb-date-eight-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 14% 18%, rgba(59, 130, 246, 0.18), transparent 34%),
    radial-gradient(circle at 86% 14%, rgba(16, 185, 129, 0.18), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfdf5 55%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-eight-card {
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(22px, 4vw, 36px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 26px 80px rgba(15, 23, 42, 0.10);
}

.vb-date-eight-header {
  max-width: 760px;
  margin-bottom: 22px;
}

.vb-date-eight-kicker {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-eight-header h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-eight-header p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-eight-body {
  display: grid;
  grid-template-columns: minmax(280px, 0.82fr) minmax(0, 1.18fr);
  gap: 18px;
}

.vb-date-eight-inputs {
  display: grid;
  align-content: start;
  gap: 14px;
  min-width: 0;
  padding: 20px;
  border-radius: 26px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-date-eight-inputs label {
  display: grid;
  gap: 8px;
}

.vb-date-eight-inputs label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-eight-inputs input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-eight-inputs input:focus {
  border-color: rgba(37, 99, 235, 0.78);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
}

.vb-date-eight-inputs button {
  min-height: 48px;
  padding: 12px 16px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #10b981);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.22);
}

.vb-date-eight-inputs small {
  min-height: 20px;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 13px;
  line-height: 1.4;
  font-weight: 750;
}

.vb-date-eight-results {
  display: grid;
  gap: 14px;
  min-width: 0;
}

.vb-date-eight-main-result {
  display: grid;
  gap: 9px;
  min-width: 0;
  padding: clamp(22px, 4vw, 32px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 12% 18%, rgba(96, 165, 250, 0.20), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #064e3b 100%) !important;
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-date-eight-main-result span,
.vb-date-eight-metrics span {
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-eight-main-result strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 64px);
  line-height: 0.98;
  font-weight: 950;
  letter-spacing: -0.065em;
  overflow-wrap: anywhere;
}

.vb-date-eight-main-result small {
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 15px;
  line-height: 1.5;
  font-weight: 650;
}

.vb-date-eight-metrics {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-eight-metrics div {
  min-width: 0;
  display: grid;
  gap: 7px;
  padding: 18px;
  border-radius: 22px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-date-eight-metrics span {
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
}

.vb-date-eight-metrics strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(20px, 3vw, 32px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.045em;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-date-eight-body {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-date-eight-card,
  .vb-date-eight-inputs,
  .vb-date-eight-main-result {
    border-radius: 24px;
  }

  .vb-date-eight-metrics {
    grid-template-columns: 1fr;
  }

  .vb-date-eight-inputs button {
    width: 100%;
  }
}

This JavaScript date difference calculator is useful for rental calculators, travel planning tools, project timelines, subscription dashboards, booking ranges, event planning pages, service estimates, and deadline tracking components.

9. Relative Time Label Generator

A relative time label generator makes dates easier to understand by turning timestamps into labels like “just now”, “5 minutes ago”, “yesterday”, or “3 days ago”. This is useful for blogs, comments, notifications, dashboards, activity feeds, and message lists.

This example uses JavaScript to compare each timestamp with the current time. It automatically generates human-friendly relative time labels and refreshes them every minute.

Example 09

Relative Time Labels

JavaScript converts timestamps into friendly labels that are easier to scan inside activity feeds.

N

New lead submitted a quote request

Website project request received from the contact form.

Loading...
U

User updated booking details

The appointment date was changed inside the dashboard.

Loading...
S

System saved draft automatically

Local browser data was saved for the unfinished form.

Loading...
M

Message was sent to the client

The project update email was delivered successfully.

Loading...

JavaScript

(function () {
  const feed = document.querySelector("[data-vb-date-nine]");
  if (!feed) return;

  const items = feed.querySelectorAll("[data-relative-time]");

  function getRelativeLabel(date) {
    const now = new Date();
    const diffMs = now - date;
    const diffSeconds = Math.floor(diffMs / 1000);
    const diffMinutes = Math.floor(diffSeconds / 60);
    const diffHours = Math.floor(diffMinutes / 60);
    const diffDays = Math.floor(diffHours / 24);

    if (diffSeconds < 60) {
      return "just now";
    }

    if (diffMinutes < 60) {
      return diffMinutes + " minute" + (diffMinutes === 1 ? "" : "s") + " ago";
    }

    if (diffHours < 24) {
      return diffHours + " hour" + (diffHours === 1 ? "" : "s") + " ago";
    }

    if (diffDays === 1) {
      return "yesterday";
    }

    return diffDays + " days ago";
  }

  function updateRelativeTimes() {
    items.forEach(function (item) {
      const offsetMinutes = Number(item.getAttribute("data-time-offset"));
      const itemDate = new Date(Date.now() - offsetMinutes * 60 * 1000);
      const label = item.querySelector("span");

      label.textContent = getRelativeLabel(itemDate);
    });
  }

  updateRelativeTimes();
  setInterval(updateRelativeTimes, 60000);
})();

HTML

<div class="vb-date-nine-demo">
  <div class="vb-date-nine-shell" data-vb-date-nine>
    <div class="vb-date-nine-header">
      <span class="vb-date-nine-kicker">Example 09</span>
      <h3>Relative Time Labels</h3>
      <p>JavaScript converts timestamps into friendly labels that are easier to scan inside activity feeds.</p>
    </div>

    <div class="vb-date-nine-feed">
      <article data-relative-time data-time-offset="2">
        <div class="vb-date-nine-icon">N</div>
        <div>
          <h4>New lead submitted a quote request</h4>
          <p>Website project request received from the contact form.</p>
          <span>Loading...</span>
        </div>
      </article>

      <article data-relative-time data-time-offset="18">
        <div class="vb-date-nine-icon">U</div>
        <div>
          <h4>User updated booking details</h4>
          <p>The appointment date was changed inside the dashboard.</p>
          <span>Loading...</span>
        </div>
      </article>

      <article data-relative-time data-time-offset="180">
        <div class="vb-date-nine-icon">S</div>
        <div>
          <h4>System saved draft automatically</h4>
          <p>Local browser data was saved for the unfinished form.</p>
          <span>Loading...</span>
        </div>
      </article>

      <article data-relative-time data-time-offset="1440">
        <div class="vb-date-nine-icon">M</div>
        <div>
          <h4>Message was sent to the client</h4>
          <p>The project update email was delivered successfully.</p>
          <span>Loading...</span>
        </div>
      </article>
    </div>
  </div>
</div>

CSS

.vb-date-nine-demo,
.vb-date-nine-demo * {
  box-sizing: border-box;
}

.vb-date-nine-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 14% 16%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(168, 85, 247, 0.18), transparent 34%),
    linear-gradient(135deg, #ecfeff 0%, #faf5ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-nine-shell {
  display: grid;
  grid-template-columns: minmax(280px, 0.78fr) minmax(0, 1.22fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-nine-header {
  min-width: 0;
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 36px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 18% 18%, rgba(34, 211, 238, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #581c87 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.22);
}

.vb-date-nine-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-nine-header h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 68px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-nine-header p {
  margin: 0 !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-nine-feed {
  display: grid;
  gap: 12px;
  min-width: 0;
}

.vb-date-nine-feed article {
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  min-width: 0;
  padding: 16px;
  border-radius: 24px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 54px rgba(15, 23, 42, 0.08);
}

.vb-date-nine-icon {
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  border-radius: 20px;
  background: linear-gradient(135deg, #06b6d4, #7c3aed);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
  box-shadow: 0 14px 34px rgba(124, 58, 237, 0.20);
}

.vb-date-nine-feed h4 {
  margin: 0 0 6px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 17px !important;
  line-height: 1.25 !important;
  font-weight: 900 !important;
  overflow-wrap: anywhere;
}

.vb-date-nine-feed p {
  margin: 0 0 9px !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-date-nine-feed span {
  display: inline-flex;
  padding: 7px 10px;
  border-radius: 999px;
  background: #ecfeff;
  color: #0891b2 !important;
  -webkit-text-fill-color: #0891b2 !important;
  font-size: 12px;
  line-height: 1.2;
  font-weight: 950;
}

@media (max-width: 900px) {
  .vb-date-nine-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-date-nine-header,
  .vb-date-nine-feed article {
    border-radius: 24px;
  }

  .vb-date-nine-feed article {
    grid-template-columns: 1fr;
  }

  .vb-date-nine-icon {
    width: 52px;
    height: 52px;
    border-radius: 18px;
  }
}

This JavaScript relative time label example is useful for comment sections, notification panels, activity feeds, message lists, blog post metadata, user dashboards, CRM timelines, and SaaS admin interfaces.

10. Meeting Time Slot Generator

A meeting time slot generator is a useful JavaScript date and time example for booking pages, consultation forms, appointment systems, agency websites, support calendars, and service scheduling tools. It creates selectable time slots between a start and end time.

This example lets users choose a meeting date and generate 30-minute time slots between 09:00 and 17:00. JavaScript builds the available slots dynamically and lets the visitor select one option.

Example 10

Meeting Time Slots

Select a date and JavaScript generates available 30-minute meeting slots for that day.

Selected meeting No slot selected

Choose a date to generate available time slots.

JavaScript

(function () {
  const generator = document.querySelector("[data-vb-date-ten]");
  if (!generator) return;

  const dateInput = generator.querySelector("[data-slot-date]");
  const generateButton = generator.querySelector("[data-generate-slots]");
  const slotList = generator.querySelector("[data-slot-list]");
  const selectedSlot = generator.querySelector("[data-selected-slot]");

  function formatInputDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");

    return year + "-" + month + "-" + day;
  }

  function formatReadableDate(value) {
    const parts = value.split("-");
    const date = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));

    return new Intl.DateTimeFormat("en", {
      month: "short",
      day: "numeric",
      year: "numeric"
    }).format(date);
  }

  function generateSlots() {
    slotList.innerHTML = "";

    if (!dateInput.value) {
      slotList.innerHTML = "<p>Please choose a date first.</p>";
      selectedSlot.textContent = "No slot selected";
      return;
    }

    const slots = [];
    let hour = 9;
    let minute = 0;

    while (hour < 17) {
      const time = String(hour).padStart(2, "0") + ":" + String(minute).padStart(2, "0");
      slots.push(time);

      minute += 30;

      if (minute === 60) {
        minute = 0;
        hour += 1;
      }
    }

    slots.forEach(function (slot) {
      const button = document.createElement("button");
      button.type = "button";
      button.className = "vb-date-ten-slot";
      button.textContent = slot;

      button.addEventListener("click", function () {
        generator.querySelectorAll(".vb-date-ten-slot").forEach(function (item) {
          item.classList.remove("is-selected");
        });

        button.classList.add("is-selected");
        selectedSlot.textContent = formatReadableDate(dateInput.value) + " at " + slot;
      });

      slotList.appendChild(button);
    });
  }

  dateInput.value = formatInputDate(new Date());
  generateButton.addEventListener("click", generateSlots);
  dateInput.addEventListener("change", generateSlots);

  generateSlots();
})();

HTML

<div class="vb-date-ten-demo">
  <div class="vb-date-ten-shell" data-vb-date-ten>
    <div class="vb-date-ten-left">
      <span class="vb-date-ten-kicker">Example 10</span>
      <h3>Meeting Time Slots</h3>
      <p>Select a date and JavaScript generates available 30-minute meeting slots for that day.</p>

      <label>
        <span>Meeting date</span>
        <input type="date" data-slot-date>
      </label>

      <button type="button" data-generate-slots>Generate Slots</button>
    </div>

    <div class="vb-date-ten-right">
      <div class="vb-date-ten-summary">
        <span>Selected meeting</span>
        <strong data-selected-slot>No slot selected</strong>
      </div>

      <div class="vb-date-ten-slots" data-slot-list>
        <p>Choose a date to generate available time slots.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-ten-demo,
.vb-date-ten-demo * {
  box-sizing: border-box;
}

.vb-date-ten-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(34, 197, 94, 0.16), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(14, 165, 233, 0.18), transparent 34%),
    linear-gradient(135deg, #ecfdf5 0%, #eff6ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-ten-shell {
  display: grid;
  grid-template-columns: minmax(280px, 0.78fr) minmax(0, 1.22fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-ten-left,
.vb-date-ten-right {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-ten-left {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 36px);
  background: #ffffff;
}

.vb-date-ten-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-ten-left h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-ten-left p {
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-ten-left label {
  display: grid;
  gap: 8px;
  margin-bottom: 14px;
}

.vb-date-ten-left label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-ten-left input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-ten-left input:focus {
  border-color: rgba(34, 197, 94, 0.78);
  box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.12);
}

.vb-date-ten-left button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #16a34a, #0ea5e9);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(14, 165, 233, 0.22);
}

.vb-date-ten-right {
  display: grid;
  gap: 14px;
  padding: clamp(20px, 4vw, 30px);
  background:
    radial-gradient(circle at 18% 16%, rgba(34, 197, 94, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #164e63 100%) !important;
}

.vb-date-ten-summary {
  display: grid;
  gap: 8px;
  padding: 20px;
  border-radius: 24px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-ten-summary span {
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-ten-summary strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(24px, 4vw, 40px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.05em;
  overflow-wrap: anywhere;
}

.vb-date-ten-slots {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  min-width: 0;
}

.vb-date-ten-slots p {
  grid-column: 1 / -1;
  margin: 0 !important;
  padding: 18px;
  border-radius: 20px;
  background: rgba(255,255,255,0.10);
  border: 1px dashed rgba(255,255,255,0.20);
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 750;
}

.vb-date-ten-slot {
  min-width: 0;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  line-height: 1.2;
  font-weight: 900;
  cursor: pointer;
  transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}

.vb-date-ten-slot:hover,
.vb-date-ten-slot.is-selected {
  transform: translateY(-2px);
  background: rgba(34, 197, 94, 0.24);
  border-color: rgba(134, 239, 172, 0.55);
}

@media (max-width: 900px) {
  .vb-date-ten-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-date-ten-left,
  .vb-date-ten-right,
  .vb-date-ten-summary {
    border-radius: 24px;
  }

  .vb-date-ten-left button {
    width: 100%;
  }

  .vb-date-ten-slots {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 420px) {
  .vb-date-ten-slots {
    grid-template-columns: 1fr;
  }
}

This JavaScript meeting time slot generator is useful for appointment booking forms, agency consultation pages, service scheduling tools, support call pages, coaching websites, and custom WordPress booking interfaces.

11. Simple Monthly Calendar Generator

A monthly calendar generator is a useful JavaScript date and time example for event websites, booking interfaces, dashboards, content planners, appointment systems, and project tools. It creates a visual calendar grid from JavaScript date calculations.

This example generates the current month automatically, highlights today, and lets users move between previous and next months. The calendar grid is responsive and avoids overflow on small screens.

Example 11

Monthly Calendar

Loading month...
Mon Tue Wed Thu Fri Sat Sun

JavaScript

(function () {
  const calendar = document.querySelector("[data-vb-date-eleven]");
  if (!calendar) return;

  const monthLabel = calendar.querySelector("[data-current-month]");
  const grid = calendar.querySelector("[data-calendar-grid]");
  const prevButton = calendar.querySelector("[data-prev-month]");
  const nextButton = calendar.querySelector("[data-next-month]");

  let visibleDate = new Date();

  function renderCalendar() {
    grid.innerHTML = "";

    const year = visibleDate.getFullYear();
    const month = visibleDate.getMonth();

    const firstDay = new Date(year, month, 1);
    const lastDay = new Date(year, month + 1, 0);
    const today = new Date();

    monthLabel.textContent = new Intl.DateTimeFormat("en", {
      month: "long",
      year: "numeric"
    }).format(firstDay);

    const firstWeekday = (firstDay.getDay() + 6) % 7;
    const daysInMonth = lastDay.getDate();

    for (let i = 0; i < firstWeekday; i += 1) {
      const emptyDay = document.createElement("div");
      emptyDay.className = "vb-date-eleven-day is-empty";
      grid.appendChild(emptyDay);
    }

    for (let day = 1; day <= daysInMonth; day += 1) {
      const dayElement = document.createElement("div");
      dayElement.className = "vb-date-eleven-day";
      dayElement.textContent = day;

      const isToday =
        day === today.getDate() &&
        month === today.getMonth() &&
        year === today.getFullYear();

      if (isToday) {
        dayElement.classList.add("is-today");
      }

      grid.appendChild(dayElement);
    }
  }

  prevButton.addEventListener("click", function () {
    visibleDate.setMonth(visibleDate.getMonth() - 1);
    renderCalendar();
  });

  nextButton.addEventListener("click", function () {
    visibleDate.setMonth(visibleDate.getMonth() + 1);
    renderCalendar();
  });

  renderCalendar();
})();

HTML

<div class="vb-date-eleven-demo">
  <div class="vb-date-eleven-calendar" data-vb-date-eleven>
    <div class="vb-date-eleven-top">
      <div>
        <span class="vb-date-eleven-kicker">Example 11</span>
        <h3>Monthly Calendar</h3>
      </div>

      <div class="vb-date-eleven-controls">
        <button type="button" data-prev-month>Prev</button>
        <button type="button" data-next-month>Next</button>
      </div>
    </div>

    <div class="vb-date-eleven-month" data-current-month>Loading month...</div>

    <div class="vb-date-eleven-weekdays">
      <span>Mon</span>
      <span>Tue</span>
      <span>Wed</span>
      <span>Thu</span>
      <span>Fri</span>
      <span>Sat</span>
      <span>Sun</span>
    </div>

    <div class="vb-date-eleven-grid" data-calendar-grid></div>
  </div>
</div>

CSS

.vb-date-eleven-demo,
.vb-date-eleven-demo * {
  box-sizing: border-box;
}

.vb-date-eleven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 14% 16%, rgba(245, 158, 11, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(239, 68, 68, 0.14), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #fef2f2 55%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-eleven-calendar {
  max-width: 980px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 18% 14%, rgba(251, 191, 36, 0.18), transparent 34%),
    linear-gradient(135deg, #111827 0%, #7c2d12 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 28px 90px rgba(124, 45, 18, 0.24);
  overflow: hidden;
}

.vb-date-eleven-top {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: flex-start;
  margin-bottom: 18px;
}

.vb-date-eleven-kicker {
  display: inline-flex;
  margin-bottom: 12px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-eleven-top h3 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-eleven-controls {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.vb-date-eleven-controls button {
  min-height: 42px;
  padding: 10px 14px;
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-eleven-month {
  margin: 0 0 16px;
  padding: 16px 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(22px, 4vw, 34px);
  line-height: 1.1;
  font-weight: 950;
  letter-spacing: -0.04em;
}

.vb-date-eleven-weekdays,
.vb-date-eleven-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 8px;
  min-width: 0;
}

.vb-date-eleven-weekdays {
  margin-bottom: 8px;
}

.vb-date-eleven-weekdays span {
  min-width: 0;
  padding: 10px 4px;
  border-radius: 14px;
  background: rgba(255,255,255,0.08);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  line-height: 1.1;
  font-weight: 950;
  text-align: center;
}

.vb-date-eleven-day {
  min-width: 0;
  min-height: 70px;
  padding: 10px;
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 900;
  text-align: left;
}

.vb-date-eleven-day.is-empty {
  opacity: 0.35;
}

.vb-date-eleven-day.is-today {
  background: linear-gradient(135deg, #f97316, #ef4444);
  border-color: rgba(255,255,255,0.32);
  box-shadow: 0 14px 34px rgba(249, 115, 22, 0.28);
}

@media (max-width: 680px) {
  .vb-date-eleven-top {
    flex-direction: column;
  }

  .vb-date-eleven-controls {
    width: 100%;
    justify-content: stretch;
  }

  .vb-date-eleven-controls button {
    flex: 1;
  }

  .vb-date-eleven-weekdays,
  .vb-date-eleven-grid {
    gap: 5px;
  }

  .vb-date-eleven-weekdays span {
    font-size: 10px;
    padding: 8px 2px;
    border-radius: 10px;
  }

  .vb-date-eleven-day {
    min-height: 48px;
    padding: 7px;
    border-radius: 12px;
    font-size: 12px;
  }
}

@media (max-width: 420px) {
  .vb-date-eleven-calendar {
    border-radius: 24px;
  }

  .vb-date-eleven-day {
    min-height: 42px;
    padding: 6px 4px;
    text-align: center;
  }
}

This JavaScript monthly calendar generator is useful for event calendars, appointment systems, dashboard widgets, content planners, booking interfaces, project tools, and WordPress pages that need a simple dynamic calendar component.

12. Countdown Progress Bar with Deadline Percentage

A countdown progress bar combines date calculation with visual progress. It is useful for project deadlines, limited-time offers, campaign timers, course enrollment pages, fundraising goals, event registration, and dashboard widgets.

This example compares a start date and deadline date. JavaScript calculates how much time has passed, how much time remains, and updates a progress bar percentage without letting the UI overflow.

Example 12

Deadline Progress

JavaScript calculates progress between a start date and deadline, then updates a visual progress bar.

Progress --%
Remaining --
Total days --

Select dates to calculate deadline progress.

JavaScript

(function () {
  const progress = document.querySelector("[data-vb-date-twelve]");
  if (!progress) return;

  const startInput = progress.querySelector("[data-progress-start]");
  const endInput = progress.querySelector("[data-progress-end]");
  const button = progress.querySelector("[data-progress-calculate]");
  const error = progress.querySelector("[data-progress-error]");

  const percentOutput = progress.querySelector("[data-progress-percent]");
  const bar = progress.querySelector("[data-progress-bar]");
  const daysLeftOutput = progress.querySelector("[data-days-left]");
  const totalRangeOutput = progress.querySelector("[data-total-range]");
  const message = progress.querySelector("[data-progress-message]");

  function formatInputDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");

    return year + "-" + month + "-" + day;
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function updateProgress() {
    error.textContent = "";

    if (!startInput.value || !endInput.value) {
      error.textContent = "Please select both dates.";
      return;
    }

    const startDate = createLocalDate(startInput.value);
    const endDate = createLocalDate(endInput.value);
    const today = new Date();

    startDate.setHours(0, 0, 0, 0);
    endDate.setHours(0, 0, 0, 0);
    today.setHours(0, 0, 0, 0);

    if (endDate <= startDate) {
      error.textContent = "Deadline must be after the start date.";
      return;
    }

    const totalMs = endDate - startDate;
    const passedMs = today - startDate;
    const remainingMs = endDate - today;

    const totalDays = Math.round(totalMs / (1000 * 60 * 60 * 24));
    const daysLeft = Math.max(0, Math.ceil(remainingMs / (1000 * 60 * 60 * 24)));

    let percent = Math.round((passedMs / totalMs) * 100);
    percent = Math.max(0, Math.min(100, percent));

    percentOutput.textContent = percent + "%";
    bar.style.width = percent + "%";
    daysLeftOutput.textContent = daysLeft + " days";
    totalRangeOutput.textContent = totalDays + " days";

    if (percent >= 100) {
      message.textContent = "The deadline has been reached or passed.";
    } else if (percent === 0) {
      message.textContent = "The timeline has not started yet.";
    } else {
      message.textContent = percent + "% of the timeline has passed. There are " + daysLeft + " days remaining.";
    }
  }

  function setDefaultDates() {
    const start = new Date();
    const end = new Date();

    start.setDate(start.getDate() - 7);
    end.setDate(end.getDate() + 21);

    startInput.value = formatInputDate(start);
    endInput.value = formatInputDate(end);

    updateProgress();
  }

  button.addEventListener("click", updateProgress);
  startInput.addEventListener("change", updateProgress);
  endInput.addEventListener("change", updateProgress);

  setDefaultDates();
})();

HTML

<div class="vb-date-twelve-demo">
  <div class="vb-date-twelve-card" data-vb-date-twelve>
    <div class="vb-date-twelve-content">
      <span class="vb-date-twelve-kicker">Example 12</span>
      <h3>Deadline Progress</h3>
      <p>JavaScript calculates progress between a start date and deadline, then updates a visual progress bar.</p>

      <div class="vb-date-twelve-inputs">
        <label>
          <span>Start date</span>
          <input type="date" data-progress-start>
        </label>

        <label>
          <span>Deadline</span>
          <input type="date" data-progress-end>
        </label>
      </div>

      <button type="button" data-progress-calculate>Update Progress</button>
      <small data-progress-error></small>
    </div>

    <div class="vb-date-twelve-panel">
      <div class="vb-date-twelve-number">
        <span>Progress</span>
        <strong data-progress-percent>--%</strong>
      </div>

      <div class="vb-date-twelve-bar">
        <span data-progress-bar></span>
      </div>

      <div class="vb-date-twelve-stats">
        <div>
          <span>Remaining</span>
          <strong data-days-left>--</strong>
        </div>
        <div>
          <span>Total days</span>
          <strong data-total-range>--</strong>
        </div>
      </div>

      <p data-progress-message>Select dates to calculate deadline progress.</p>
    </div>
  </div>
</div>

CSS

.vb-date-twelve-demo,
.vb-date-twelve-demo * {
  box-sizing: border-box;
}

.vb-date-twelve-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 16%, rgba(124, 58, 237, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(6, 182, 212, 0.18), transparent 34%),
    linear-gradient(135deg, #f5f3ff 0%, #ecfeff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(124, 58, 237, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twelve-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 430px);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-twelve-content,
.vb-date-twelve-panel {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-twelve-content {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 36px);
  background: #ffffff;
}

.vb-date-twelve-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twelve-content h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-twelve-content p {
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-twelve-inputs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 14px;
}

.vb-date-twelve-inputs label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-twelve-inputs label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-twelve-inputs input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-twelve-inputs input:focus {
  border-color: rgba(124, 58, 237, 0.78);
  box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.12);
}

.vb-date-twelve-content button {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #7c3aed, #06b6d4);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(124, 58, 237, 0.22);
}

.vb-date-twelve-content small {
  display: block;
  min-height: 20px;
  margin-top: 10px;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 13px;
  line-height: 1.4;
  font-weight: 750;
}

.vb-date-twelve-panel {
  display: grid;
  align-content: center;
  gap: 18px;
  padding: clamp(22px, 4vw, 34px);
  background:
    radial-gradient(circle at 14% 16%, rgba(34, 211, 238, 0.22), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #312e81 100%) !important;
  overflow: hidden;
}

.vb-date-twelve-number {
  display: grid;
  gap: 7px;
}

.vb-date-twelve-number span,
.vb-date-twelve-stats span {
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-twelve-number strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(54px, 9vw, 92px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.075em;
}

.vb-date-twelve-bar {
  width: 100%;
  height: 18px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twelve-bar span {
  display: block;
  width: 0%;
  max-width: 100%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(135deg, #22d3ee, #7c3aed);
  transition: width 0.35s ease;
}

.vb-date-twelve-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-twelve-stats div {
  min-width: 0;
  padding: 15px;
  border-radius: 18px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-date-twelve-stats strong {
  display: block;
  margin-top: 5px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  line-height: 1.1;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-date-twelve-panel p {
  margin: 0 !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 900px) {
  .vb-date-twelve-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-twelve-inputs,
  .vb-date-twelve-stats {
    grid-template-columns: 1fr;
  }

  .vb-date-twelve-content,
  .vb-date-twelve-panel {
    border-radius: 24px;
  }

  .vb-date-twelve-content button {
    width: 100%;
  }
}

This JavaScript deadline progress bar is useful for project dashboards, campaign trackers, limited-time offers, event registrations, course enrollment pages, project management tools, and custom website status widgets.

Need a custom JavaScript date, calendar, booking, or calculator feature for your website?

Contact us

13. Date Input Validator with Minimum and Maximum Dates

A JavaScript date input validator is useful for booking forms, appointment systems, registration pages, delivery forms, event signups, rental calculators, and checkout flows. It prevents users from selecting invalid dates before they submit a form.

This example validates a selected date against a minimum and maximum allowed date range. JavaScript shows a success or error message immediately, making the form easier to understand and safer to use.

Example 13

Date Validator

Select a booking date. JavaScript checks whether the date is inside the allowed booking window.

Minimum date --
Maximum date --
Choose a date

Your validation result will appear here.

JavaScript

(function () {
  const validator = document.querySelector("[data-vb-date-thirteen]");
  if (!validator) return;

  const input = validator.querySelector("[data-booking-date]");
  const button = validator.querySelector("[data-validate-date]");
  const result = validator.querySelector("[data-date-result]");
  const minLabel = validator.querySelector("[data-min-label]");
  const maxLabel = validator.querySelector("[data-max-label]");

  const today = new Date();
  const minDate = new Date();
  const maxDate = new Date();

  minDate.setDate(today.getDate() + 2);
  maxDate.setDate(today.getDate() + 45);

  function formatInputDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");

    return year + "-" + month + "-" + day;
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function formatReadableDate(date) {
    return new Intl.DateTimeFormat("en", {
      month: "long",
      day: "numeric",
      year: "numeric"
    }).format(date);
  }

  function setResult(title, text, valid) {
    result.classList.remove("is-valid", "is-invalid");
    result.classList.add(valid ? "is-valid" : "is-invalid");

    result.querySelector("strong").textContent = title;
    result.querySelector("p").textContent = text;
  }

  function validateDate() {
    if (!input.value) {
      setResult("Date missing", "Please choose a booking date before validating.", false);
      return;
    }

    const selectedDate = createLocalDate(input.value);

    selectedDate.setHours(0, 0, 0, 0);
    minDate.setHours(0, 0, 0, 0);
    maxDate.setHours(0, 0, 0, 0);

    if (selectedDate < minDate) {
      setResult(
        "Too early",
        "The selected date is before the minimum allowed booking date.",
        false
      );
      return;
    }

    if (selectedDate > maxDate) {
      setResult(
        "Too late",
        "The selected date is outside the maximum allowed booking window.",
        false
      );
      return;
    }

    setResult(
      "Date accepted",
      "The selected booking date is inside the allowed date range.",
      true
    );
  }

  minLabel.textContent = formatReadableDate(minDate);
  maxLabel.textContent = formatReadableDate(maxDate);
  input.min = formatInputDate(minDate);
  input.max = formatInputDate(maxDate);
  input.value = formatInputDate(minDate);

  button.addEventListener("click", validateDate);
  input.addEventListener("change", validateDate);

  validateDate();
})();

HTML

<div class="vb-date-thirteen-demo">
  <div class="vb-date-thirteen-card" data-vb-date-thirteen>
    <div class="vb-date-thirteen-info">
      <span class="vb-date-thirteen-kicker">Example 13</span>
      <h3>Date Validator</h3>
      <p>Select a booking date. JavaScript checks whether the date is inside the allowed booking window.</p>

      <div class="vb-date-thirteen-rules">
        <div>
          <span>Minimum date</span>
          <strong data-min-label>--</strong>
        </div>
        <div>
          <span>Maximum date</span>
          <strong data-max-label>--</strong>
        </div>
      </div>
    </div>

    <div class="vb-date-thirteen-form">
      <label>
        <span>Booking date</span>
        <input type="date" data-booking-date>
      </label>

      <button type="button" data-validate-date>Validate Date</button>

      <div class="vb-date-thirteen-result" data-date-result>
        <strong>Choose a date</strong>
        <p>Your validation result will appear here.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-thirteen-demo,
.vb-date-thirteen-demo * {
  box-sizing: border-box;
}

.vb-date-thirteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(59, 130, 246, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #f0fdf4 55%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-thirteen-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 410px);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-thirteen-info,
.vb-date-thirteen-form {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-thirteen-info {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-thirteen-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-thirteen-info h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-thirteen-info p {
  max-width: 650px;
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-thirteen-rules {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-thirteen-rules div {
  min-width: 0;
  padding: 16px;
  border-radius: 20px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-date-thirteen-rules span {
  display: block;
  margin-bottom: 6px;
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.10em;
}

.vb-date-thirteen-rules strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 17px;
  line-height: 1.25;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-date-thirteen-form {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(22px, 4vw, 34px);
  background:
    radial-gradient(circle at 18% 16%, rgba(34, 197, 94, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #064e3b 100%) !important;
}

.vb-date-thirteen-form label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-thirteen-form label span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-thirteen-form input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(255,255,255,0.18);
  border-radius: 17px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-thirteen-form input::-webkit-calendar-picker-indicator {
  filter: invert(1);
  opacity: 0.9;
}

.vb-date-thirteen-form input:focus {
  border-color: rgba(134, 239, 172, 0.85);
  box-shadow: 0 0 0 4px rgba(134, 239, 172, 0.14);
}

.vb-date-thirteen-form button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #22c55e, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(34, 197, 94, 0.22);
}

.vb-date-thirteen-result {
  display: grid;
  gap: 7px;
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-thirteen-result strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  line-height: 1.1;
  font-weight: 950;
  letter-spacing: -0.04em;
  overflow-wrap: anywhere;
}

.vb-date-thirteen-result p {
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-date-thirteen-result.is-valid {
  background: rgba(34, 197, 94, 0.20);
  border-color: rgba(134, 239, 172, 0.42);
}

.vb-date-thirteen-result.is-invalid {
  background: rgba(248, 113, 113, 0.18);
  border-color: rgba(248, 113, 113, 0.36);
}

@media (max-width: 900px) {
  .vb-date-thirteen-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-thirteen-info,
  .vb-date-thirteen-form,
  .vb-date-thirteen-result {
    border-radius: 24px;
  }

  .vb-date-thirteen-rules {
    grid-template-columns: 1fr;
  }

  .vb-date-thirteen-form button {
    width: 100%;
  }
}

This JavaScript date validator is useful for appointment booking, rental date selection, event registration, delivery scheduling, checkout forms, reservation systems, and any form that should only allow dates inside a specific range.

14. Weekend Detector for Selected Date

A weekend detector is a simple but practical JavaScript date example for booking systems, delivery calculators, business forms, support availability widgets, calendar tools, and service request pages. It checks whether a selected date falls on a weekday or weekend.

This example reads a selected date, detects the weekday, and displays whether the date is a business day or weekend. It also shows a visual day card for all seven weekdays.

Example 14

Weekend Detector

Choose a date and JavaScript checks whether it is a weekday or weekend.

Weekday Select a date

Your result will appear here.

Mon
Tue
Wed
Thu
Fri
Sat
Sun

JavaScript

(function () {
  const detector = document.querySelector("[data-vb-date-fourteen]");
  if (!detector) return;

  const input = detector.querySelector("[data-weekend-date]");
  const button = detector.querySelector("[data-check-weekend]");
  const statusBox = detector.querySelector("[data-weekend-status]");
  const weekdayName = detector.querySelector("[data-weekday-name]");
  const result = detector.querySelector("[data-weekend-result]");
  const message = detector.querySelector("[data-weekend-message]");
  const dayBoxes = detector.querySelectorAll("[data-day-index]");

  const weekdayNames = [
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday"
  ];

  function formatInputDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");

    return year + "-" + month + "-" + day;
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function checkWeekend() {
    if (!input.value) return;

    const selectedDate = createLocalDate(input.value);
    const dayIndex = selectedDate.getDay();
    const isWeekend = dayIndex === 0 || dayIndex === 6;

    dayBoxes.forEach(function (box) {
      box.classList.toggle("is-active", Number(box.getAttribute("data-day-index")) === dayIndex);
    });

    statusBox.classList.remove("is-weekend", "is-business");
    statusBox.classList.add(isWeekend ? "is-weekend" : "is-business");

    weekdayName.textContent = weekdayNames[dayIndex];

    if (isWeekend) {
      result.textContent = "Weekend";
      message.textContent = "The selected date falls on a weekend. You may need a different business day.";
    } else {
      result.textContent = "Business day";
      message.textContent = "The selected date is a weekday and can be used for business-day logic.";
    }
  }

  input.value = formatInputDate(new Date());
  button.addEventListener("click", checkWeekend);
  input.addEventListener("change", checkWeekend);

  checkWeekend();
})();

HTML

<div class="vb-date-fourteen-demo">
  <div class="vb-date-fourteen-shell" data-vb-date-fourteen>
    <div class="vb-date-fourteen-panel">
      <span class="vb-date-fourteen-kicker">Example 14</span>
      <h3>Weekend Detector</h3>
      <p>Choose a date and JavaScript checks whether it is a weekday or weekend.</p>

      <label>
        <span>Select date</span>
        <input type="date" data-weekend-date>
      </label>

      <button type="button" data-check-weekend>Check Date</button>
    </div>

    <div class="vb-date-fourteen-result">
      <div class="vb-date-fourteen-status" data-weekend-status>
        <span data-weekday-name>Weekday</span>
        <strong data-weekend-result>Select a date</strong>
        <p data-weekend-message>Your result will appear here.</p>
      </div>

      <div class="vb-date-fourteen-days">
        <div data-day-index="1">Mon</div>
        <div data-day-index="2">Tue</div>
        <div data-day-index="3">Wed</div>
        <div data-day-index="4">Thu</div>
        <div data-day-index="5">Fri</div>
        <div data-day-index="6">Sat</div>
        <div data-day-index="0">Sun</div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-fourteen-demo,
.vb-date-fourteen-demo * {
  box-sizing: border-box;
}

.vb-date-fourteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 16%, rgba(245, 158, 11, 0.18), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #f0fdf4 55%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-fourteen-shell {
  display: grid;
  grid-template-columns: minmax(280px, 0.8fr) minmax(0, 1.2fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-fourteen-panel,
.vb-date-fourteen-result {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-fourteen-panel {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 36px);
  background: #ffffff;
}

.vb-date-fourteen-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #fef3c7;
  color: #b45309 !important;
  -webkit-text-fill-color: #b45309 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-fourteen-panel h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-fourteen-panel p {
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-fourteen-panel label {
  display: grid;
  gap: 8px;
  margin-bottom: 14px;
}

.vb-date-fourteen-panel label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-fourteen-panel input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-fourteen-panel input:focus {
  border-color: rgba(245, 158, 11, 0.78);
  box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.12);
}

.vb-date-fourteen-panel button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #f59e0b, #16a34a);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(245, 158, 11, 0.22);
}

.vb-date-fourteen-result {
  display: grid;
  align-content: center;
  gap: 16px;
  padding: clamp(20px, 4vw, 32px);
  background:
    radial-gradient(circle at 16% 12%, rgba(251, 191, 36, 0.18), transparent 34%),
    linear-gradient(135deg, #111827 0%, #365314 100%) !important;
}

.vb-date-fourteen-status {
  display: grid;
  gap: 9px;
  min-width: 0;
  padding: 22px;
  border-radius: 26px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-fourteen-status span {
  color: #fef3c7 !important;
  -webkit-text-fill-color: #fef3c7 !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-fourteen-status strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 60px);
  line-height: 0.98;
  font-weight: 950;
  letter-spacing: -0.065em;
  overflow-wrap: anywhere;
}

.vb-date-fourteen-status p {
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

.vb-date-fourteen-status.is-weekend {
  background: rgba(248, 113, 113, 0.18);
  border-color: rgba(248, 113, 113, 0.34);
}

.vb-date-fourteen-status.is-business {
  background: rgba(34, 197, 94, 0.18);
  border-color: rgba(134, 239, 172, 0.34);
}

.vb-date-fourteen-days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 8px;
}

.vb-date-fourteen-days div {
  min-width: 0;
  min-height: 48px;
  display: grid;
  place-items: center;
  border-radius: 16px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.13);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
  font-weight: 950;
}

.vb-date-fourteen-days div.is-active {
  background: linear-gradient(135deg, #f59e0b, #22c55e);
  border-color: rgba(255,255,255,0.34);
  box-shadow: 0 14px 34px rgba(245, 158, 11, 0.22);
}

@media (max-width: 900px) {
  .vb-date-fourteen-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-fourteen-panel,
  .vb-date-fourteen-result,
  .vb-date-fourteen-status {
    border-radius: 24px;
  }

  .vb-date-fourteen-panel button {
    width: 100%;
  }

  .vb-date-fourteen-days {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 420px) {
  .vb-date-fourteen-days {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

This JavaScript weekend detector is useful for business-day calculators, booking systems, delivery tools, service request forms, checkout date validation, appointment pages, and calendar widgets.

15. Time Until Next Business Day

A time until next business day widget is useful for contact pages, customer support widgets, ecommerce stores, quote request pages, agencies, clinics, and service businesses. It tells visitors when the business will be available again.

This example checks whether the current time is outside business hours and calculates the time remaining until the next business day starts. It updates every minute and handles weekends safely.

Example 15

Next Business Day

JavaScript calculates how long until the next available business opening time.

Checking status --

Loading current business availability...

Business hours Mon-Fri, 09:00-17:00
Current time --:--
Next opening --

JavaScript

(function () {
  const widget = document.querySelector("[data-vb-date-fifteen]");
  if (!widget) return;

  const statusBox = widget.querySelector("[data-business-status]");
  const statusLabel = widget.querySelector("[data-status-label]");
  const countdownLabel = widget.querySelector("[data-countdown-label]");
  const message = widget.querySelector("[data-business-message]");
  const currentTime = widget.querySelector("[data-current-time]");
  const nextOpening = widget.querySelector("[data-next-opening]");

  const openHour = 9;
  const closeHour = 17;

  function isBusinessDay(date) {
    const day = date.getDay();
    return day >= 1 && day <= 5;
  }

  function isOpenNow(date) {
    return isBusinessDay(date) && date.getHours() >= openHour && date.getHours() < closeHour;
  }

  function getNextOpening(date) {
    const next = new Date(date);
    next.setMinutes(0, 0, 0);

    if (isBusinessDay(next) && next.getHours() < openHour) {
      next.setHours(openHour);
      return next;
    }

    next.setDate(next.getDate() + 1);
    next.setHours(openHour, 0, 0, 0);

    while (!isBusinessDay(next)) {
      next.setDate(next.getDate() + 1);
    }

    return next;
  }

  function formatDuration(milliseconds) {
    const totalMinutes = Math.max(0, Math.ceil(milliseconds / (1000 * 60)));
    const days = Math.floor(totalMinutes / 1440);
    const hours = Math.floor((totalMinutes % 1440) / 60);
    const minutes = totalMinutes % 60;

    if (days > 0) {
      return days + "d " + hours + "h";
    }

    return hours + "h " + minutes + "m";
  }

  function formatDateTime(date) {
    return new Intl.DateTimeFormat("en", {
      weekday: "short",
      month: "short",
      day: "numeric",
      hour: "2-digit",
      minute: "2-digit"
    }).format(date);
  }

  function updateBusinessCountdown() {
    const now = new Date();

    currentTime.textContent = new Intl.DateTimeFormat("en", {
      hour: "2-digit",
      minute: "2-digit"
    }).format(now);

    statusBox.classList.remove("is-open", "is-closed");

    if (isOpenNow(now)) {
      statusBox.classList.add("is-open");
      statusLabel.textContent = "Open now";
      countdownLabel.textContent = "Available";
      message.textContent = "The business is currently open. Visitors can contact the team during working hours.";
      nextOpening.textContent = "Open now";
      return;
    }

    const openingDate = getNextOpening(now);
    const remainingTime = openingDate - now;

    statusBox.classList.add("is-closed");
    statusLabel.textContent = "Closed now";
    countdownLabel.textContent = formatDuration(remainingTime);
    message.textContent = "Time remaining until the next business opening.";
    nextOpening.textContent = formatDateTime(openingDate);
  }

  updateBusinessCountdown();
  setInterval(updateBusinessCountdown, 60000);
})();

HTML

<div class="vb-date-fifteen-demo">
  <div class="vb-date-fifteen-card" data-vb-date-fifteen>
    <div class="vb-date-fifteen-top">
      <span class="vb-date-fifteen-kicker">Example 15</span>
      <h3>Next Business Day</h3>
      <p>JavaScript calculates how long until the next available business opening time.</p>
    </div>

    <div class="vb-date-fifteen-status" data-business-status>
      <span data-status-label>Checking status</span>
      <strong data-countdown-label>--</strong>
      <p data-business-message>Loading current business availability...</p>
    </div>

    <div class="vb-date-fifteen-info-grid">
      <div>
        <span>Business hours</span>
        <strong>Mon-Fri, 09:00-17:00</strong>
      </div>
      <div>
        <span>Current time</span>
        <strong data-current-time>--:--</strong>
      </div>
      <div>
        <span>Next opening</span>
        <strong data-next-opening>--</strong>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-fifteen-demo,
.vb-date-fifteen-demo * {
  box-sizing: border-box;
}

.vb-date-fifteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #ecfeff 0%, #f0fdf4 55%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-fifteen-card {
  max-width: 1040px;
  margin: 0 auto;
  padding: clamp(22px, 5vw, 44px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 211, 238, 0.20), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(34, 197, 94, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #064e3b 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
  overflow: hidden;
}

.vb-date-fifteen-top {
  max-width: 760px;
  margin-bottom: 24px;
}

.vb-date-fifteen-kicker {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-fifteen-top h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(38px, 7vw, 76px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-fifteen-top p {
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-fifteen-status {
  display: grid;
  gap: 10px;
  min-width: 0;
  margin-bottom: 16px;
  padding: clamp(20px, 4vw, 32px);
  border-radius: 28px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-fifteen-status span {
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-fifteen-status strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 7vw, 74px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}

.vb-date-fifteen-status p {
  margin: 0 !important;
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

.vb-date-fifteen-status.is-open {
  background: rgba(34, 197, 94, 0.18);
  border-color: rgba(134, 239, 172, 0.34);
}

.vb-date-fifteen-status.is-closed {
  background: rgba(14, 165, 233, 0.16);
  border-color: rgba(125, 211, 252, 0.30);
}

.vb-date-fifteen-info-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-fifteen-info-grid div {
  min-width: 0;
  padding: 16px;
  border-radius: 20px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-date-fifteen-info-grid span {
  display: block;
  margin-bottom: 6px;
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 11px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.09em;
}

.vb-date-fifteen-info-grid strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.3;
  font-weight: 850;
  overflow-wrap: anywhere;
}

@media (max-width: 760px) {
  .vb-date-fifteen-info-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 520px) {
  .vb-date-fifteen-card,
  .vb-date-fifteen-status {
    border-radius: 24px;
  }

  .vb-date-fifteen-status strong {
    font-size: 40px;
  }
}

This JavaScript next business day widget is useful for contact pages, support availability sections, local business websites, ecommerce customer service widgets, quote request forms, agency websites, and booking interfaces.

16. Custom Date Range Picker Summary

A custom date range picker summary is useful for booking forms, hotel reservations, rental websites, dashboards, analytics filters, project planners, and ecommerce reports. It lets users select a start and end date, then JavaScript creates a clear range summary.

This example validates the selected range, calculates the total number of nights or days, and updates a clean summary card. The layout is fully responsive and keeps all text inside the cards on small screens.

Example 16

Date Range Summary

Select a start and end date. JavaScript validates the range and creates a readable booking-style summary.

Selected range Select dates

Your date range summary will appear here.

Start --
End --
Total --

JavaScript

(function () {
  const picker = document.querySelector("[data-vb-date-sixteen]");
  if (!picker) return;

  const startInput = picker.querySelector("[data-range-start]");
  const endInput = picker.querySelector("[data-range-end]");
  const button = picker.querySelector("[data-range-update]");
  const error = picker.querySelector("[data-range-error]");

  const title = picker.querySelector("[data-range-title]");
  const text = picker.querySelector("[data-range-text]");
  const startLabel = picker.querySelector("[data-range-start-label]");
  const endLabel = picker.querySelector("[data-range-end-label]");
  const totalLabel = picker.querySelector("[data-range-total]");

  function formatInputDate(date) {
    return date.getFullYear() + "-" +
      String(date.getMonth() + 1).padStart(2, "0") + "-" +
      String(date.getDate()).padStart(2, "0");
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function formatReadableDate(date) {
    return new Intl.DateTimeFormat("en", {
      month: "short",
      day: "numeric",
      year: "numeric"
    }).format(date);
  }

  function updateRange() {
    error.textContent = "";

    if (!startInput.value || !endInput.value) {
      error.textContent = "Please select both start and end dates.";
      return;
    }

    const startDate = createLocalDate(startInput.value);
    const endDate = createLocalDate(endInput.value);

    if (endDate < startDate) {
      error.textContent = "End date must be after the start date.";
      title.textContent = "Invalid range";
      text.textContent = "Please choose an end date that comes after the start date.";
      startLabel.textContent = "--";
      endLabel.textContent = "--";
      totalLabel.textContent = "--";
      return;
    }

    const diffMs = endDate - startDate;
    const totalDays = Math.round(diffMs / (1000 * 60 * 60 * 24));

    title.textContent = totalDays + " day" + (totalDays === 1 ? "" : "s");
    text.textContent = "Your selected range runs from " + formatReadableDate(startDate) + " to " + formatReadableDate(endDate) + ".";
    startLabel.textContent = formatReadableDate(startDate);
    endLabel.textContent = formatReadableDate(endDate);
    totalLabel.textContent = totalDays + " days";
  }

  function setDefaultDates() {
    const start = new Date();
    const end = new Date();
    end.setDate(start.getDate() + 5);

    startInput.value = formatInputDate(start);
    endInput.value = formatInputDate(end);

    updateRange();
  }

  button.addEventListener("click", updateRange);
  startInput.addEventListener("change", updateRange);
  endInput.addEventListener("change", updateRange);

  setDefaultDates();
})();

HTML

<div class="vb-date-sixteen-demo">
  <div class="vb-date-sixteen-card" data-vb-date-sixteen>
    <div class="vb-date-sixteen-form">
      <span class="vb-date-sixteen-kicker">Example 16</span>
      <h3>Date Range Summary</h3>
      <p>Select a start and end date. JavaScript validates the range and creates a readable booking-style summary.</p>

      <div class="vb-date-sixteen-inputs">
        <label>
          <span>Start date</span>
          <input type="date" data-range-start>
        </label>

        <label>
          <span>End date</span>
          <input type="date" data-range-end>
        </label>
      </div>

      <button type="button" data-range-update>Update Range</button>
      <small data-range-error></small>
    </div>

    <div class="vb-date-sixteen-summary">
      <div class="vb-date-sixteen-main">
        <span>Selected range</span>
        <strong data-range-title>Select dates</strong>
        <p data-range-text>Your date range summary will appear here.</p>
      </div>

      <div class="vb-date-sixteen-metrics">
        <div>
          <span>Start</span>
          <strong data-range-start-label>--</strong>
        </div>
        <div>
          <span>End</span>
          <strong data-range-end-label>--</strong>
        </div>
        <div>
          <span>Total</span>
          <strong data-range-total>--</strong>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-sixteen-demo,
.vb-date-sixteen-demo * {
  box-sizing: border-box;
}

.vb-date-sixteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(37, 99, 235, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #faf5ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-sixteen-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(310px, 430px);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-sixteen-form,
.vb-date-sixteen-summary {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-sixteen-form {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-sixteen-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-sixteen-form h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-sixteen-form p {
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-sixteen-inputs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 14px;
}

.vb-date-sixteen-inputs label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-sixteen-inputs span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-sixteen-inputs input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-sixteen-inputs input:focus {
  border-color: rgba(37, 99, 235, 0.78);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
}

.vb-date-sixteen-form button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #7c3aed);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.22);
}

.vb-date-sixteen-form small {
  display: block;
  min-height: 20px;
  margin-top: 10px;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 13px;
  line-height: 1.4;
  font-weight: 750;
}

.vb-date-sixteen-summary {
  display: grid;
  gap: 14px;
  padding: clamp(20px, 4vw, 32px);
  background:
    radial-gradient(circle at 12% 16%, rgba(129, 140, 248, 0.20), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #312e81 100%) !important;
}

.vb-date-sixteen-main {
  display: grid;
  gap: 9px;
  min-width: 0;
  padding: 22px;
  border-radius: 26px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-sixteen-main span,
.vb-date-sixteen-metrics span {
  color: #c4b5fd !important;
  -webkit-text-fill-color: #c4b5fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-sixteen-main strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 5vw, 52px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-date-sixteen-main p {
  margin: 0 !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

.vb-date-sixteen-metrics {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}

.vb-date-sixteen-metrics div {
  min-width: 0;
  padding: 14px;
  border-radius: 18px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-date-sixteen-metrics strong {
  display: block;
  margin-top: 5px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 16px;
  line-height: 1.3;
  font-weight: 850;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-date-sixteen-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-sixteen-inputs {
    grid-template-columns: 1fr;
  }

  .vb-date-sixteen-form,
  .vb-date-sixteen-summary,
  .vb-date-sixteen-main {
    border-radius: 24px;
  }

  .vb-date-sixteen-form button {
    width: 100%;
  }
}

This JavaScript date range summary example is useful for hotel bookings, rental forms, dashboard filters, analytics date ranges, reporting tools, travel planners, project timelines, and reservation interfaces.

17. Event Schedule Timeline by Date

An event schedule timeline is useful for conference websites, webinar pages, course schedules, launch events, workshops, festival pages, and internal dashboards. It organizes time-based events in a clean visual timeline.

This example uses a small JavaScript event array and renders a schedule timeline automatically. Users can filter between all events, today’s events, and upcoming events.

Example 17

Event Timeline

JavaScript renders a schedule timeline from event data and filters events by date status.

JavaScript

(function () {
  const timeline = document.querySelector("[data-vb-date-seventeen]");
  if (!timeline) return;

  const list = timeline.querySelector("[data-timeline-list]");
  const filters = timeline.querySelectorAll("[data-filter]");

  const today = new Date();
  today.setHours(0, 0, 0, 0);

  const events = [
    {
      title: "Opening Keynote",
      description: "A practical introduction to the event topic and main schedule.",
      offset: 0,
      time: "10:00"
    },
    {
      title: "JavaScript Workshop",
      description: "Hands-on session with date, form, and UI examples.",
      offset: 1,
      time: "13:30"
    },
    {
      title: "Client Demo Review",
      description: "Review the interface and timeline with the project team.",
      offset: 3,
      time: "15:00"
    },
    {
      title: "Final Q&A Session",
      description: "Live questions, feedback, and next steps.",
      offset: 7,
      time: "11:00"
    }
  ];

  function getEventDate(offset) {
    const date = new Date(today);
    date.setDate(today.getDate() + offset);
    return date;
  }

  function renderEvents(filter) {
    list.innerHTML = "";

    const visibleEvents = events.filter(function (event) {
      if (filter === "today") return event.offset === 0;
      if (filter === "upcoming") return event.offset > 0;
      return true;
    });

    if (visibleEvents.length === 0) {
      list.innerHTML = '<p class="vb-date-seventeen-empty">No events found for this filter.</p>';
      return;
    }

    visibleEvents.forEach(function (event) {
      const eventDate = getEventDate(event.offset);
      const day = new Intl.DateTimeFormat("en", { day: "2-digit" }).format(eventDate);
      const month = new Intl.DateTimeFormat("en", { month: "short" }).format(eventDate);

      const item = document.createElement("article");
      item.className = "vb-date-seventeen-item";

      item.innerHTML =
        '<div class="vb-date-seventeen-date">' +
          '<strong>' + day + '</strong>' +
          '<span>' + month + '</span>' +
        '</div>' +
        '<div class="vb-date-seventeen-content">' +
          '<small>' + event.time + '</small>' +
          '<h4>' + event.title + '</h4>' +
          '<p>' + event.description + '</p>' +
        '</div>';

      list.appendChild(item);
    });
  }

  filters.forEach(function (button) {
    button.addEventListener("click", function () {
      filters.forEach(function (item) {
        item.classList.remove("is-active");
      });

      button.classList.add("is-active");
      renderEvents(button.getAttribute("data-filter"));
    });
  });

  filters[0].classList.add("is-active");
  renderEvents("all");
})();

HTML

<div class="vb-date-seventeen-demo">
  <div class="vb-date-seventeen-shell" data-vb-date-seventeen>
    <div class="vb-date-seventeen-header">
      <span class="vb-date-seventeen-kicker">Example 17</span>
      <h3>Event Timeline</h3>
      <p>JavaScript renders a schedule timeline from event data and filters events by date status.</p>

      <div class="vb-date-seventeen-filters">
        <button type="button" data-filter="all">All</button>
        <button type="button" data-filter="today">Today</button>
        <button type="button" data-filter="upcoming">Upcoming</button>
      </div>
    </div>

    <div class="vb-date-seventeen-list" data-timeline-list></div>
  </div>
</div>

CSS

.vb-date-seventeen-demo,
.vb-date-seventeen-demo * {
  box-sizing: border-box;
}

.vb-date-seventeen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(249, 115, 22, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(236, 72, 153, 0.15), transparent 34%),
    linear-gradient(135deg, #fff7ed 0%, #fdf2f8 55%, #ffffff 100%) !important;
  border: 1px solid rgba(249, 115, 22, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-seventeen-shell {
  display: grid;
  grid-template-columns: minmax(280px, 0.82fr) minmax(0, 1.18fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-seventeen-header,
.vb-date-seventeen-list {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-seventeen-header {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 36px);
  background:
    radial-gradient(circle at 16% 14%, rgba(251, 146, 60, 0.18), transparent 34%),
    linear-gradient(135deg, #111827 0%, #7c2d12 100%) !important;
}

.vb-date-seventeen-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-seventeen-header h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 68px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-seventeen-header p {
  margin: 0 0 22px !important;
  color: #ffedd5 !important;
  -webkit-text-fill-color: #ffedd5 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-seventeen-filters {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.vb-date-seventeen-filters button {
  min-height: 42px;
  padding: 10px 14px;
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-seventeen-filters button.is-active {
  background: linear-gradient(135deg, #f97316, #db2777);
  border-color: rgba(255,255,255,0.32);
}

.vb-date-seventeen-list {
  display: grid;
  gap: 12px;
  padding: clamp(18px, 4vw, 28px);
  background: #ffffff;
}

.vb-date-seventeen-item {
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr);
  gap: 14px;
  min-width: 0;
  padding: 16px;
  border-radius: 24px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-date-seventeen-date {
  display: grid;
  place-items: center;
  align-content: center;
  min-height: 86px;
  padding: 10px;
  border-radius: 20px;
  background: linear-gradient(135deg, #f97316, #db2777);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-date-seventeen-date strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 26px;
  line-height: 1;
  font-weight: 950;
}

.vb-date-seventeen-date span {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-date-seventeen-content {
  min-width: 0;
  display: grid;
  align-content: center;
  gap: 6px;
}

.vb-date-seventeen-content h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.25 !important;
  font-weight: 900 !important;
  overflow-wrap: anywhere;
}

.vb-date-seventeen-content p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-date-seventeen-content small {
  color: #ea580c !important;
  -webkit-text-fill-color: #ea580c !important;
  font-size: 12px;
  font-weight: 950;
}

.vb-date-seventeen-empty {
  margin: 0 !important;
  padding: 20px;
  border-radius: 22px;
  background: #f8fafc;
  border: 1px dashed rgba(148, 163, 184, 0.45);
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 15px;
  line-height: 1.55;
  font-weight: 750;
}

@media (max-width: 900px) {
  .vb-date-seventeen-shell {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-seventeen-header,
  .vb-date-seventeen-list,
  .vb-date-seventeen-item {
    border-radius: 24px;
  }

  .vb-date-seventeen-item {
    grid-template-columns: 1fr;
  }

  .vb-date-seventeen-date {
    width: 100%;
    min-height: 70px;
  }

  .vb-date-seventeen-filters button {
    flex: 1;
  }
}

This JavaScript event schedule timeline is useful for webinar pages, conference schedules, workshop pages, launch timelines, course schedules, dashboard event lists, and content planning interfaces.

18. Time-Based Greeting Message

A time-based greeting message is a simple but effective JavaScript date and time feature for dashboards, hero sections, membership areas, customer portals, admin panels, and landing pages. It changes the message based on the visitor’s current time.

This example detects morning, afternoon, evening, and night with JavaScript. It then updates the greeting text, helper message, and visual state automatically.

Example 18

Loading greeting...

JavaScript is checking the current time.

Current local time --:--
Time period

JavaScript

(function () {
  const greeting = document.querySelector("[data-vb-date-eighteen]");
  if (!greeting) return;

  const title = greeting.querySelector("[data-greeting-title]");
  const message = greeting.querySelector("[data-greeting-message]");
  const time = greeting.querySelector("[data-greeting-time]");
  const period = greeting.querySelector("[data-greeting-period]");
  const icon = greeting.querySelector("[data-greeting-icon]");

  function updateGreeting() {
    const now = new Date();
    const hour = now.getHours();

    greeting.classList.remove("is-morning", "is-afternoon", "is-evening", "is-night");

    time.textContent = new Intl.DateTimeFormat("en", {
      hour: "2-digit",
      minute: "2-digit"
    }).format(now);

    if (hour >= 5 && hour < 12) {
      greeting.classList.add("is-morning");
      title.textContent = "Good morning";
      message.textContent = "Start the day with a clear schedule, fresh tasks, and updated website data.";
      period.textContent = "Morning";
      icon.textContent = "☀";
      return;
    }

    if (hour >= 12 && hour < 17) {
      greeting.classList.add("is-afternoon");
      title.textContent = "Good afternoon";
      message.textContent = "A smart dashboard can show time-based messages, tasks, reminders, and useful user context.";
      period.textContent = "Afternoon";
      icon.textContent = "●";
      return;
    }

    if (hour >= 17 && hour < 22) {
      greeting.classList.add("is-evening");
      title.textContent = "Good evening";
      message.textContent = "Use JavaScript time checks to personalize landing pages, portals, and customer dashboards.";
      period.textContent = "Evening";
      icon.textContent = "◐";
      return;
    }

    greeting.classList.add("is-night");
    title.textContent = "Good night";
    message.textContent = "Night mode messages can help dashboards feel more personal and context-aware.";
    period.textContent = "Night";
    icon.textContent = "☾";
  }

  updateGreeting();
  setInterval(updateGreeting, 60000);
})();

HTML

<div class="vb-date-eighteen-demo">
  <div class="vb-date-eighteen-card" data-vb-date-eighteen>
    <div class="vb-date-eighteen-glow"></div>

    <div class="vb-date-eighteen-content">
      <span class="vb-date-eighteen-kicker">Example 18</span>
      <h3 data-greeting-title>Loading greeting...</h3>
      <p data-greeting-message>JavaScript is checking the current time.</p>

      <div class="vb-date-eighteen-time">
        <span>Current local time</span>
        <strong data-greeting-time>--:--</strong>
      </div>
    </div>

    <div class="vb-date-eighteen-status">
      <span data-greeting-period>Time period</span>
      <strong data-greeting-icon>●</strong>
    </div>
  </div>
</div>

CSS

.vb-date-eighteen-demo,
.vb-date-eighteen-demo * {
  box-sizing: border-box;
}

.vb-date-eighteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(14, 165, 233, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(245, 158, 11, 0.16), transparent 34%),
    linear-gradient(135deg, #ecfeff 0%, #fffbeb 55%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-eighteen-card {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(180px, 260px);
  gap: 18px;
  max-width: 1060px;
  margin: 0 auto;
  overflow: hidden;
  padding: clamp(24px, 5vw, 48px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 16% 14%, rgba(34, 211, 238, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #1e3a8a 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
}

.vb-date-eighteen-glow {
  position: absolute;
  right: -90px;
  top: -90px;
  width: 280px;
  height: 280px;
  border-radius: 999px;
  background: rgba(34, 211, 238, 0.22);
  filter: blur(8px);
  pointer-events: none;
}

.vb-date-eighteen-card.is-morning {
  background:
    radial-gradient(circle at 16% 14%, rgba(253, 186, 116, 0.24), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #7c2d12 100%) !important;
}

.vb-date-eighteen-card.is-afternoon {
  background:
    radial-gradient(circle at 16% 14%, rgba(96, 165, 250, 0.24), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #1e40af 100%) !important;
}

.vb-date-eighteen-card.is-evening {
  background:
    radial-gradient(circle at 16% 14%, rgba(168, 85, 247, 0.24), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #581c87 100%) !important;
}

.vb-date-eighteen-card.is-night {
  background:
    radial-gradient(circle at 16% 14%, rgba(148, 163, 184, 0.20), transparent 34%),
    linear-gradient(135deg, #020617 0%, #111827 100%) !important;
}

.vb-date-eighteen-content {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-date-eighteen-kicker {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-eighteen-content h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(38px, 7vw, 78px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-date-eighteen-content p {
  max-width: 660px;
  margin: 0 0 24px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-eighteen-time {
  display: inline-grid;
  gap: 7px;
  min-width: min(100%, 260px);
  padding: 16px 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-eighteen-time span {
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-date-eighteen-time strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 28px;
  line-height: 1;
  font-weight: 950;
  font-variant-numeric: tabular-nums;
}

.vb-date-eighteen-status {
  position: relative;
  z-index: 2;
  align-self: stretch;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 14px;
  min-width: 0;
  padding: 20px;
  border-radius: 28px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-eighteen-status span {
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 13px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  text-align: center;
}

.vb-date-eighteen-status strong {
  display: grid;
  place-items: center;
  width: 88px;
  height: 88px;
  border-radius: 999px;
  background: linear-gradient(135deg, #22d3ee, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 36px;
  line-height: 1;
  font-weight: 950;
  box-shadow: 0 18px 42px rgba(37, 99, 235, 0.30);
}

@media (max-width: 820px) {
  .vb-date-eighteen-card {
    grid-template-columns: 1fr;
  }

  .vb-date-eighteen-status {
    min-height: 160px;
  }
}

@media (max-width: 520px) {
  .vb-date-eighteen-card,
  .vb-date-eighteen-status {
    border-radius: 24px;
  }

  .vb-date-eighteen-time {
    width: 100%;
  }
}

This JavaScript time-based greeting example is useful for dashboards, user portals, SaaS apps, admin panels, homepage hero sections, membership websites, and personalized landing pages.

Need a custom JavaScript date, calendar, booking, or calculator feature for your website?

Contact us

19. Auto-Renewing Daily Deal Countdown

An auto-renewing daily deal countdown is useful for ecommerce stores, landing pages, promo banners, SaaS offers, course pages, and campaign sections. Unlike a fixed countdown, this timer never permanently ends because it automatically resets for the next day.

This example counts down to midnight. When the day ends, JavaScript automatically starts the next daily cycle, updates the date label, and keeps the countdown running forever without needing a manual date change.

Example 19

Daily Deal Countdown

This countdown automatically resets every day at midnight, so the example keeps working forever.

Current deal cycle Loading...
00 Hours
00 Minutes
00 Seconds

JavaScript

(function () {
  const countdown = document.querySelector("[data-vb-date-nineteen]");
  if (!countdown) return;

  const hoursElement = countdown.querySelector("[data-hours]");
  const minutesElement = countdown.querySelector("[data-minutes]");
  const secondsElement = countdown.querySelector("[data-seconds]");
  const cycleLabel = countdown.querySelector("[data-cycle-label]");

  function pad(value) {
    return String(value).padStart(2, "0");
  }

  function getNextMidnight(now) {
    const midnight = new Date(now);
    midnight.setDate(now.getDate() + 1);
    midnight.setHours(0, 0, 0, 0);
    return midnight;
  }

  function updateCountdown() {
    const now = new Date();
    const nextMidnight = getNextMidnight(now);
    const remaining = nextMidnight - now;

    const totalSeconds = Math.max(0, Math.floor(remaining / 1000));
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const seconds = totalSeconds % 60;

    hoursElement.textContent = pad(hours);
    minutesElement.textContent = pad(minutes);
    secondsElement.textContent = pad(seconds);

    cycleLabel.textContent = "Ends today, renews tomorrow automatically";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-date-nineteen-demo">
  <div class="vb-date-nineteen-card" data-vb-date-nineteen>
    <div class="vb-date-nineteen-left">
      <span class="vb-date-nineteen-kicker">Example 19</span>
      <h3>Daily Deal Countdown</h3>
      <p>This countdown automatically resets every day at midnight, so the example keeps working forever.</p>

      <div class="vb-date-nineteen-label">
        <span>Current deal cycle</span>
        <strong data-cycle-label>Loading...</strong>
      </div>
    </div>

    <div class="vb-date-nineteen-timer">
      <div>
        <strong data-hours>00</strong>
        <span>Hours</span>
      </div>
      <div>
        <strong data-minutes>00</strong>
        <span>Minutes</span>
      </div>
      <div>
        <strong data-seconds>00</strong>
        <span>Seconds</span>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-nineteen-demo,
.vb-date-nineteen-demo * {
  box-sizing: border-box;
}

.vb-date-nineteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 14% 16%, rgba(248, 113, 113, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(245, 158, 11, 0.18), transparent 34%),
    linear-gradient(135deg, #fef2f2 0%, #fffbeb 55%, #ffffff 100%) !important;
  border: 1px solid rgba(248, 113, 113, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-nineteen-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 480px);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(22px, 5vw, 44px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(251, 146, 60, 0.22), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(239, 68, 68, 0.22), transparent 34%),
    linear-gradient(135deg, #111827 0%, #7f1d1d 58%, #9a3412 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 90px rgba(127, 29, 29, 0.28);
  overflow: hidden;
}

.vb-date-nineteen-left {
  display: grid;
  align-content: center;
  min-width: 0;
}

.vb-date-nineteen-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-nineteen-left h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 7vw, 76px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-date-nineteen-left p {
  max-width: 620px;
  margin: 0 0 22px !important;
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-nineteen-label {
  display: inline-grid;
  gap: 7px;
  max-width: 100%;
  padding: 16px 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-nineteen-label span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.10em;
}

.vb-date-nineteen-label strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  line-height: 1.25;
  font-weight: 900;
  overflow-wrap: anywhere;
}

.vb-date-nineteen-timer {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  min-width: 0;
  align-content: center;
}

.vb-date-nineteen-timer div {
  display: grid;
  place-items: center;
  gap: 9px;
  min-width: 0;
  min-height: 170px;
  padding: 18px 12px;
  border-radius: 26px;
  background: rgba(255,255,255,0.11);
  border: 1px solid rgba(255,255,255,0.16);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10);
}

.vb-date-nineteen-timer strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(40px, 7vw, 70px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.065em;
  font-variant-numeric: tabular-nums;
}

.vb-date-nineteen-timer span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.10em;
}

@media (max-width: 920px) {
  .vb-date-nineteen-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-nineteen-card,
  .vb-date-nineteen-timer div {
    border-radius: 24px;
  }

  .vb-date-nineteen-timer {
    grid-template-columns: 1fr;
  }

  .vb-date-nineteen-timer div {
    min-height: 110px;
  }
}

This JavaScript daily deal countdown is useful for ecommerce banners, promo sections, landing pages, SaaS offers, daily campaigns, course promotions, and any timer that should automatically renew instead of permanently ending.

20. Auto-Rolling Weekly Event Countdown

An auto-rolling weekly event countdown is useful for recurring webinars, weekly meetings, live streams, online classes, community events, and support sessions. It does not use a fixed one-time deadline, so it keeps working week after week.

This example counts down to the next Monday at 10:00. After that time passes, JavaScript automatically targets the following Monday, keeping the countdown active forever.

Example 20

Weekly Event Countdown

This countdown always targets the next Monday event and automatically rolls forward every week.

Next session Loading...
00 Days
00 Hours
00 Minutes
00 Seconds

JavaScript

(function () {
  const countdown = document.querySelector("[data-vb-date-twenty]");
  if (!countdown) return;

  const nextSession = countdown.querySelector("[data-next-session]");
  const daysElement = countdown.querySelector("[data-days]");
  const hoursElement = countdown.querySelector("[data-hours]");
  const minutesElement = countdown.querySelector("[data-minutes]");
  const secondsElement = countdown.querySelector("[data-seconds]");

  const targetWeekday = 1;
  const targetHour = 10;

  function pad(value) {
    return String(value).padStart(2, "0");
  }

  function getNextWeeklyEvent(now) {
    const target = new Date(now);
    target.setHours(targetHour, 0, 0, 0);

    const currentDay = now.getDay();
    let daysUntilTarget = (targetWeekday - currentDay + 7) % 7;

    if (daysUntilTarget === 0 && target <= now) {
      daysUntilTarget = 7;
    }

    target.setDate(now.getDate() + daysUntilTarget);
    return target;
  }

  function updateCountdown() {
    const now = new Date();
    const target = getNextWeeklyEvent(now);
    const distance = target - now;

    const totalSeconds = Math.max(0, Math.floor(distance / 1000));
    const days = Math.floor(totalSeconds / 86400);
    const hours = Math.floor((totalSeconds % 86400) / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const seconds = totalSeconds % 60;

    daysElement.textContent = pad(days);
    hoursElement.textContent = pad(hours);
    minutesElement.textContent = pad(minutes);
    secondsElement.textContent = pad(seconds);

    nextSession.textContent = new Intl.DateTimeFormat("en", {
      weekday: "long",
      month: "long",
      day: "numeric",
      hour: "2-digit",
      minute: "2-digit"
    }).format(target);
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-date-twenty-demo">
  <div class="vb-date-twenty-card" data-vb-date-twenty>
    <div class="vb-date-twenty-header">
      <span class="vb-date-twenty-kicker">Example 20</span>
      <h3>Weekly Event Countdown</h3>
      <p>This countdown always targets the next Monday event and automatically rolls forward every week.</p>
    </div>

    <div class="vb-date-twenty-next">
      <span>Next session</span>
      <strong data-next-session>Loading...</strong>
    </div>

    <div class="vb-date-twenty-grid">
      <div>
        <strong data-days>00</strong>
        <span>Days</span>
      </div>
      <div>
        <strong data-hours>00</strong>
        <span>Hours</span>
      </div>
      <div>
        <strong data-minutes>00</strong>
        <span>Minutes</span>
      </div>
      <div>
        <strong data-seconds>00</strong>
        <span>Seconds</span>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-twenty-demo,
.vb-date-twenty-demo * {
  box-sizing: border-box;
}

.vb-date-twenty-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 14% 16%, rgba(37, 99, 235, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfeff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twenty-card {
  max-width: 1080px;
  margin: 0 auto;
  padding: clamp(22px, 5vw, 44px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 16%, rgba(56, 189, 248, 0.20), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #1e3a8a 58%, #164e63 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
  overflow: hidden;
}

.vb-date-twenty-header {
  max-width: 760px;
  margin-bottom: 20px;
}

.vb-date-twenty-kicker {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twenty-header h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 7vw, 76px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-date-twenty-header p {
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-twenty-next {
  display: grid;
  gap: 7px;
  margin-bottom: 16px;
  padding: 18px;
  border-radius: 24px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twenty-next span {
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-twenty-next strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(22px, 4vw, 36px);
  line-height: 1.1;
  font-weight: 950;
  letter-spacing: -0.04em;
  overflow-wrap: anywhere;
}

.vb-date-twenty-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-twenty-grid div {
  display: grid;
  place-items: center;
  gap: 9px;
  min-width: 0;
  min-height: 155px;
  padding: 18px 10px;
  border-radius: 24px;
  background: rgba(255,255,255,0.11);
  border: 1px solid rgba(255,255,255,0.15);
}

.vb-date-twenty-grid strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 64px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.06em;
  font-variant-numeric: tabular-nums;
}

.vb-date-twenty-grid span {
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

@media (max-width: 760px) {
  .vb-date-twenty-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-date-twenty-grid div {
    min-height: 130px;
  }
}

@media (max-width: 460px) {
  .vb-date-twenty-card,
  .vb-date-twenty-grid div {
    border-radius: 24px;
  }

  .vb-date-twenty-grid {
    grid-template-columns: 1fr;
  }

  .vb-date-twenty-grid div {
    min-height: 105px;
  }
}

This JavaScript weekly countdown is useful for recurring webinars, weekly live streams, online classes, community events, support sessions, coaching calls, and recurring appointment pages.

21. Infinite Rotating Date Quote Widget

An infinite rotating date quote widget is a simple JavaScript time-based component that keeps changing content forever. It is useful for dashboards, homepages, blog sidebars, daily motivation sections, learning platforms, and member portals.

This example selects a quote based on the current day number. Because the calculation uses the current date dynamically, the widget keeps rotating through the list forever without needing fixed dates.

Today Loading date...
Example 21

Daily Rotating Quote

Loading quote...

This widget changes automatically based on the current date and keeps cycling forever.

JavaScript

(function () {
  const widget = document.querySelector("[data-vb-date-twentyone]");
  if (!widget) return;

  const dateLabel = widget.querySelector("[data-date-label]");
  const quote = widget.querySelector("[data-daily-quote]");
  const note = widget.querySelector("[data-daily-note]");

  const quotes = [
    "Small improvements become powerful systems when they repeat every day.",
    "A useful website remembers context, time, and user intent.",
    "Good JavaScript turns static pages into practical tools.",
    "Responsive details matter because real users visit from every screen size.",
    "The best interface is clear, fast, and helpful at the right moment.",
    "Automation is most valuable when it keeps working without manual updates.",
    "A simple time-based feature can make a website feel much smarter."
  ];

  function getDayNumber(date) {
    const start = new Date(date.getFullYear(), 0, 0);
    const difference = date - start;
    return Math.floor(difference / (1000 * 60 * 60 * 24));
  }

  function updateDailyQuote() {
    const now = new Date();
    const dayNumber = getDayNumber(now);
    const quoteIndex = dayNumber % quotes.length;

    dateLabel.textContent = new Intl.DateTimeFormat("en", {
      weekday: "long",
      month: "long",
      day: "numeric"
    }).format(now);

    quote.textContent = quotes[quoteIndex];
    note.textContent = "Quote " + (quoteIndex + 1) + " of " + quotes.length + " is selected automatically from the current date.";
  }

  updateDailyQuote();
  setInterval(updateDailyQuote, 60000);
})();

HTML

<div class="vb-date-twentyone-demo">
  <div class="vb-date-twentyone-card" data-vb-date-twentyone>
    <div class="vb-date-twentyone-date">
      <span>Today</span>
      <strong data-date-label>Loading date...</strong>
    </div>

    <div class="vb-date-twentyone-content">
      <span class="vb-date-twentyone-kicker">Example 21</span>
      <h3>Daily Rotating Quote</h3>
      <blockquote data-daily-quote>Loading quote...</blockquote>
      <p data-daily-note>This widget changes automatically based on the current date and keeps cycling forever.</p>
    </div>
  </div>
</div>

CSS

.vb-date-twentyone-demo,
.vb-date-twentyone-demo * {
  box-sizing: border-box;
}

.vb-date-twentyone-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 14% 16%, rgba(168, 85, 247, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(34, 211, 238, 0.16), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #ecfeff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentyone-card {
  display: grid;
  grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
  gap: 20px;
  max-width: 1060px;
  margin: 0 auto;
  padding: clamp(22px, 5vw, 44px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 16%, rgba(168, 85, 247, 0.20), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(34, 211, 238, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #312e81 58%, #164e63 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
  overflow: hidden;
}

.vb-date-twentyone-date {
  display: grid;
  align-content: center;
  gap: 10px;
  min-width: 0;
  padding: 22px;
  border-radius: 28px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentyone-date span {
  color: #c4b5fd !important;
  -webkit-text-fill-color: #c4b5fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-twentyone-date strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 5vw, 48px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.055em;
  overflow-wrap: anywhere;
}

.vb-date-twentyone-content {
  display: grid;
  align-content: center;
  min-width: 0;
}

.vb-date-twentyone-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentyone-content h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-twentyone-content blockquote {
  max-width: 720px;
  margin: 0 0 18px !important;
  padding: 0;
  border: 0;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(24px, 4vw, 42px);
  line-height: 1.12;
  font-weight: 850;
  letter-spacing: -0.045em;
  overflow-wrap: anywhere;
}

.vb-date-twentyone-content p {
  max-width: 680px;
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

@media (max-width: 820px) {
  .vb-date-twentyone-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 520px) {
  .vb-date-twentyone-card,
  .vb-date-twentyone-date {
    border-radius: 24px;
  }
}

This JavaScript daily rotating quote widget is useful for homepages, dashboards, blog sidebars, member areas, learning platforms, admin panels, and any website section that should update automatically without fixed end dates.

22. Auto-Renewing Monthly Goal Tracker

An auto-renewing monthly goal tracker is useful for dashboards, fitness apps, habit trackers, sales goals, subscription platforms, project dashboards, and business reports. It shows month progress and automatically resets every new month.

This example calculates how far the current month has progressed. Because it uses the current month dynamically, it keeps working forever and automatically starts a new cycle every month.

Example 22

Monthly Goal Tracker

JavaScript calculates the current month progress and automatically starts a new cycle every month.

Current month Loading...
Month progress --%
Passed --
Remaining --
Total --

JavaScript

(function () {
  const tracker = document.querySelector("[data-vb-date-twentytwo]");
  if (!tracker) return;

  const monthLabel = tracker.querySelector("[data-month-label]");
  const percentLabel = tracker.querySelector("[data-month-percent]");
  const progressBar = tracker.querySelector("[data-month-bar]");
  const daysPassed = tracker.querySelector("[data-days-passed]");
  const daysRemaining = tracker.querySelector("[data-days-remaining]");
  const daysTotal = tracker.querySelector("[data-days-total]");

  function updateMonthProgress() {
    const now = new Date();
    const year = now.getFullYear();
    const month = now.getMonth();

    const firstDay = new Date(year, month, 1);
    const nextMonth = new Date(year, month + 1, 1);
    const lastDay = new Date(year, month + 1, 0);

    const totalDays = lastDay.getDate();
    const passed = now.getDate();
    const remaining = Math.max(0, totalDays - passed);

    const percent = Math.max(0, Math.min(100, Math.round(((now - firstDay) / (nextMonth - firstDay)) * 100)));

    monthLabel.textContent = new Intl.DateTimeFormat("en", {
      month: "long",
      year: "numeric"
    }).format(now);

    percentLabel.textContent = percent + "%";
    progressBar.style.width = percent + "%";
    daysPassed.textContent = passed;
    daysRemaining.textContent = remaining;
    daysTotal.textContent = totalDays;
  }

  updateMonthProgress();
  setInterval(updateMonthProgress, 60000);
})();

HTML

<div class="vb-date-twentytwo-demo">
  <div class="vb-date-twentytwo-card" data-vb-date-twentytwo>
    <div class="vb-date-twentytwo-left">
      <span class="vb-date-twentytwo-kicker">Example 22</span>
      <h3>Monthly Goal Tracker</h3>
      <p>JavaScript calculates the current month progress and automatically starts a new cycle every month.</p>

      <div class="vb-date-twentytwo-month">
        <span>Current month</span>
        <strong data-month-label>Loading...</strong>
      </div>
    </div>

    <div class="vb-date-twentytwo-right">
      <div class="vb-date-twentytwo-percent">
        <span>Month progress</span>
        <strong data-month-percent>--%</strong>
      </div>

      <div class="vb-date-twentytwo-bar">
        <span data-month-bar></span>
      </div>

      <div class="vb-date-twentytwo-stats">
        <div>
          <span>Passed</span>
          <strong data-days-passed>--</strong>
        </div>
        <div>
          <span>Remaining</span>
          <strong data-days-remaining>--</strong>
        </div>
        <div>
          <span>Total</span>
          <strong data-days-total>--</strong>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-twentytwo-demo,
.vb-date-twentytwo-demo * {
  box-sizing: border-box;
}

.vb-date-twentytwo-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 197, 94, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #eff6ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentytwo-card {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(320px, 1.1fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(22px, 5vw, 44px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 16%, rgba(34, 197, 94, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(59, 130, 246, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #14532d 55%, #1e3a8a 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
  overflow: hidden;
}

.vb-date-twentytwo-left,
.vb-date-twentytwo-right {
  min-width: 0;
}

.vb-date-twentytwo-left {
  display: grid;
  align-content: center;
}

.vb-date-twentytwo-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentytwo-left h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 7vw, 74px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-date-twentytwo-left p {
  max-width: 620px;
  margin: 0 0 22px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-twentytwo-month {
  display: inline-grid;
  gap: 7px;
  max-width: 100%;
  padding: 16px 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentytwo-month span,
.vb-date-twentytwo-percent span,
.vb-date-twentytwo-stats span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.vb-date-twentytwo-month strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 20px;
  line-height: 1.25;
  font-weight: 900;
  overflow-wrap: anywhere;
}

.vb-date-twentytwo-right {
  display: grid;
  align-content: center;
  gap: 18px;
  padding: clamp(20px, 4vw, 30px);
  border-radius: 28px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-date-twentytwo-percent {
  display: grid;
  gap: 8px;
}

.vb-date-twentytwo-percent strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(58px, 10vw, 98px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.08em;
}

.vb-date-twentytwo-bar {
  width: 100%;
  height: 18px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentytwo-bar span {
  display: block;
  width: 0%;
  max-width: 100%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(135deg, #22c55e, #3b82f6);
  transition: width 0.35s ease;
}

.vb-date-twentytwo-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

.vb-date-twentytwo-stats div {
  min-width: 0;
  padding: 14px;
  border-radius: 18px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-date-twentytwo-stats strong {
  display: block;
  margin-top: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 20px;
  line-height: 1.1;
  font-weight: 950;
}

@media (max-width: 920px) {
  .vb-date-twentytwo-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-twentytwo-card,
  .vb-date-twentytwo-right {
    border-radius: 24px;
  }

  .vb-date-twentytwo-stats {
    grid-template-columns: 1fr;
  }
}

This JavaScript monthly goal tracker is useful for dashboards, goal apps, sales reports, habit trackers, analytics widgets, business reports, subscription portals, and any monthly cycle that should automatically renew forever.

23. Infinite Pomodoro Work Timer

An infinite Pomodoro work timer is useful for productivity apps, learning platforms, dashboards, focus tools, online courses, and time management widgets. It keeps switching between work and break sessions automatically.

This example uses a repeating 25-minute work session and 5-minute break session. When one session reaches zero, JavaScript immediately starts the next session, so the timer keeps running forever.

Example 23

Infinite Pomodoro Timer

This timer automatically alternates between work and break sessions, so it keeps working forever.

Work session 25:00

Focus mode is ready.

JavaScript

(function () {
  const pomodoro = document.querySelector("[data-vb-date-twentythree]");
  if (!pomodoro) return;

  const timerBox = pomodoro.querySelector(".vb-date-twentythree-timer");
  const sessionLabel = pomodoro.querySelector("[data-session-label]");
  const timeLeft = pomodoro.querySelector("[data-time-left]");
  const sessionNote = pomodoro.querySelector("[data-session-note]");

  const startButton = pomodoro.querySelector("[data-start-timer]");
  const pauseButton = pomodoro.querySelector("[data-pause-timer]");
  const resetButton = pomodoro.querySelector("[data-reset-timer]");

  const workSeconds = 25 * 60;
  const breakSeconds = 5 * 60;

  let mode = "work";
  let remainingSeconds = workSeconds;
  let interval = null;

  function formatTime(seconds) {
    const minutes = Math.floor(seconds / 60);
    const restSeconds = seconds % 60;

    return String(minutes).padStart(2, "0") + ":" + String(restSeconds).padStart(2, "0");
  }

  function renderTimer() {
    timerBox.setAttribute("data-mode", mode);
    timeLeft.textContent = formatTime(remainingSeconds);

    if (mode === "work") {
      sessionLabel.textContent = "Work session";
      sessionNote.textContent = "Focus mode is active. The timer will switch to break automatically.";
    } else {
      sessionLabel.textContent = "Break session";
      sessionNote.textContent = "Break mode is active. The timer will switch back to work automatically.";
    }
  }

  function switchSession() {
    if (mode === "work") {
      mode = "break";
      remainingSeconds = breakSeconds;
    } else {
      mode = "work";
      remainingSeconds = workSeconds;
    }

    renderTimer();
  }

  function tick() {
    remainingSeconds -= 1;

    if (remainingSeconds <= 0) {
      switchSession();
      return;
    }

    renderTimer();
  }

  function startTimer() {
    if (interval) return;
    interval = setInterval(tick, 1000);
  }

  function pauseTimer() {
    clearInterval(interval);
    interval = null;
  }

  function resetTimer() {
    pauseTimer();
    mode = "work";
    remainingSeconds = workSeconds;
    renderTimer();
  }

  startButton.addEventListener("click", startTimer);
  pauseButton.addEventListener("click", pauseTimer);
  resetButton.addEventListener("click", resetTimer);

  renderTimer();
})();

HTML

<div class="vb-date-twentythree-demo">
  <div class="vb-date-twentythree-card" data-vb-date-twentythree>
    <div class="vb-date-twentythree-header">
      <span class="vb-date-twentythree-kicker">Example 23</span>
      <h3>Infinite Pomodoro Timer</h3>
      <p>This timer automatically alternates between work and break sessions, so it keeps working forever.</p>
    </div>

    <div class="vb-date-twentythree-timer" data-mode="work">
      <span data-session-label>Work session</span>
      <strong data-time-left>25:00</strong>
      <p data-session-note>Focus mode is ready.</p>
    </div>

    <div class="vb-date-twentythree-controls">
      <button type="button" data-start-timer>Start</button>
      <button type="button" data-pause-timer>Pause</button>
      <button type="button" data-reset-timer>Reset</button>
    </div>
  </div>
</div>

CSS

.vb-date-twentythree-demo,
.vb-date-twentythree-demo * {
  box-sizing: border-box;
}

.vb-date-twentythree-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 14% 16%, rgba(236, 72, 153, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(124, 58, 237, 0.16), transparent 34%),
    linear-gradient(135deg, #fdf2f8 0%, #f5f3ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(236, 72, 153, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentythree-card {
  max-width: 980px;
  margin: 0 auto;
  padding: clamp(22px, 5vw, 46px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 18% 16%, rgba(236, 72, 153, 0.22), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(124, 58, 237, 0.20), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #581c87 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 90px rgba(88, 28, 135, 0.26);
  overflow: hidden;
}

.vb-date-twentythree-header {
  max-width: 760px;
  margin-bottom: 22px;
}

.vb-date-twentythree-kicker {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentythree-header h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 7vw, 76px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-date-twentythree-header p {
  margin: 0 !important;
  color: #e9d5ff !important;
  -webkit-text-fill-color: #e9d5ff !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-twentythree-timer {
  display: grid;
  place-items: center;
  gap: 12px;
  min-width: 0;
  min-height: 300px;
  margin-bottom: 16px;
  padding: 24px;
  border-radius: 30px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentythree-timer[data-mode="break"] {
  background: rgba(34, 197, 94, 0.14);
  border-color: rgba(134, 239, 172, 0.32);
}

.vb-date-twentythree-timer span {
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-twentythree-timer strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(72px, 15vw, 150px);
  line-height: 0.88;
  font-weight: 950;
  letter-spacing: -0.08em;
  font-variant-numeric: tabular-nums;
}

.vb-date-twentythree-timer p {
  max-width: 520px;
  margin: 0 !important;
  color: #e9d5ff !important;
  -webkit-text-fill-color: #e9d5ff !important;
  font-size: 15px;
  line-height: 1.55;
  font-weight: 650;
  text-align: center;
}

.vb-date-twentythree-controls {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.vb-date-twentythree-controls button {
  flex: 1;
  min-width: 130px;
  min-height: 48px;
  padding: 12px 16px;
  border: 1px solid rgba(255,255,255,0.16);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-twentythree-controls button:first-child {
  background: linear-gradient(135deg, #db2777, #7c3aed);
  border-color: rgba(255,255,255,0.24);
}

@media (max-width: 560px) {
  .vb-date-twentythree-card,
  .vb-date-twentythree-timer {
    border-radius: 24px;
  }

  .vb-date-twentythree-timer {
    min-height: 230px;
  }

  .vb-date-twentythree-controls button {
    min-width: 100%;
  }
}

This infinite JavaScript Pomodoro timer is useful for productivity apps, focus dashboards, study tools, learning platforms, time management widgets, online courses, and personal workflow pages.

24. Always-Current Year Progress Tracker

An always-current year progress tracker is useful for dashboards, yearly goals, habit apps, financial reports, project planning pages, business dashboards, and productivity widgets. It automatically uses the current year and resets every January.

This example calculates how much of the current year has passed. It does not need a fixed year value, so it stays accurate forever as the calendar moves forward.

Example 24

Year Progress Tracker

JavaScript calculates the current year progress dynamically and automatically resets every new year.

--% Current year
Days passed --
Days remaining --
Total days --

JavaScript

(function () {
  const tracker = document.querySelector("[data-vb-date-twentyfour]");
  if (!tracker) return;

  const percent = tracker.querySelector("[data-year-percent]");
  const yearLabel = tracker.querySelector("[data-year-label]");
  const passed = tracker.querySelector("[data-year-passed]");
  const remaining = tracker.querySelector("[data-year-remaining]");
  const total = tracker.querySelector("[data-year-total]");

  function updateYearProgress() {
    const now = new Date();
    const year = now.getFullYear();

    const start = new Date(year, 0, 1);
    const end = new Date(year + 1, 0, 1);

    const totalDays = Math.round((end - start) / (1000 * 60 * 60 * 24));
    const passedDays = Math.floor((now - start) / (1000 * 60 * 60 * 24)) + 1;
    const remainingDays = Math.max(0, totalDays - passedDays);

    const progressPercent = Math.max(0, Math.min(100, Math.round(((now - start) / (end - start)) * 100)));

    percent.textContent = progressPercent + "%";
    yearLabel.textContent = "Year " + year;
    passed.textContent = passedDays;
    remaining.textContent = remainingDays;
    total.textContent = totalDays;
  }

  updateYearProgress();
  setInterval(updateYearProgress, 60000);
})();

HTML

<div class="vb-date-twentyfour-demo">
  <div class="vb-date-twentyfour-card" data-vb-date-twentyfour>
    <div class="vb-date-twentyfour-top">
      <span class="vb-date-twentyfour-kicker">Example 24</span>
      <h3>Year Progress Tracker</h3>
      <p>JavaScript calculates the current year progress dynamically and automatically resets every new year.</p>
    </div>

    <div class="vb-date-twentyfour-main">
      <div class="vb-date-twentyfour-circle">
        <strong data-year-percent>--%</strong>
        <span data-year-label>Current year</span>
      </div>

      <div class="vb-date-twentyfour-details">
        <div>
          <span>Days passed</span>
          <strong data-year-passed>--</strong>
        </div>
        <div>
          <span>Days remaining</span>
          <strong data-year-remaining>--</strong>
        </div>
        <div>
          <span>Total days</span>
          <strong data-year-total>--</strong>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-twentyfour-demo,
.vb-date-twentyfour-demo * {
  box-sizing: border-box;
}

.vb-date-twentyfour-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 16%, rgba(20, 184, 166, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdfa 0%, #eef2ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentyfour-card {
  max-width: 1080px;
  margin: 0 auto;
  padding: clamp(22px, 5vw, 46px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 16%, rgba(45, 212, 191, 0.20), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(129, 140, 248, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #134e4a 55%, #312e81 100%) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
  overflow: hidden;
}

.vb-date-twentyfour-top {
  max-width: 780px;
  margin-bottom: 24px;
}

.vb-date-twentyfour-kicker {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.16);
  color: #99f6e4 !important;
  -webkit-text-fill-color: #99f6e4 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentyfour-top h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 7vw, 76px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-twentyfour-top p {
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-date-twentyfour-main {
  display: grid;
  grid-template-columns: minmax(260px, 360px) minmax(0, 1fr);
  gap: 20px;
  align-items: stretch;
}

.vb-date-twentyfour-circle {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 8px;
  min-width: 0;
  min-height: 280px;
  padding: 24px;
  border-radius: 32px;
  background:
    radial-gradient(circle at center, rgba(45, 212, 191, 0.24), transparent 52%),
    rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.15);
}

.vb-date-twentyfour-circle strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(64px, 12vw, 118px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.085em;
}

.vb-date-twentyfour-circle span,
.vb-date-twentyfour-details span {
  color: #99f6e4 !important;
  -webkit-text-fill-color: #99f6e4 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-date-twentyfour-details {
  display: grid;
  gap: 12px;
  min-width: 0;
}

.vb-date-twentyfour-details div {
  display: grid;
  align-content: center;
  min-width: 0;
  min-height: 86px;
  padding: 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-date-twentyfour-details strong {
  display: block;
  margin-top: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(24px, 4vw, 38px);
  line-height: 1.05;
  font-weight: 950;
  letter-spacing: -0.05em;
}

@media (max-width: 820px) {
  .vb-date-twentyfour-main {
    grid-template-columns: 1fr;
  }

  .vb-date-twentyfour-circle {
    min-height: 230px;
  }
}

@media (max-width: 520px) {
  .vb-date-twentyfour-card,
  .vb-date-twentyfour-circle,
  .vb-date-twentyfour-details div {
    border-radius: 24px;
  }
}

This JavaScript year progress tracker is useful for yearly goal dashboards, business reports, productivity apps, financial planning widgets, personal trackers, project timelines, and admin panels that should always stay current without manual updates.

Need a custom JavaScript date, calendar, booking, or calculator feature for your website?

Contact us

25. JavaScript Time Zone Converter

A JavaScript time zone converter is one of the most useful date and time examples for booking systems, international websites, remote teams, webinar pages, dashboards, and event tools. It helps users convert one selected time into different time zones.

This example lets users choose a date and time, then JavaScript converts it into New York, London, Tallinn, Tokyo, and Sydney times using Intl.DateTimeFormat.

Example 25

Time Zone Converter

Select one date and time, then convert it into multiple international time zones.

New York --
London --
Tallinn --
Tokyo --
Sydney --

JavaScript

(function () {
  const converter = document.querySelector("[data-vb-date-twentyfive]");
  if (!converter) return;

  const input = converter.querySelector("[data-timezone-input]");
  const button = converter.querySelector("[data-convert-timezones]");

  const outputs = {
    newyork: converter.querySelector("[data-zone-newyork]"),
    london: converter.querySelector("[data-zone-london]"),
    tallinn: converter.querySelector("[data-zone-tallinn]"),
    tokyo: converter.querySelector("[data-zone-tokyo]"),
    sydney: converter.querySelector("[data-zone-sydney]")
  };

  const zones = {
    newyork: "America/New_York",
    london: "Europe/London",
    tallinn: "Europe/Tallinn",
    tokyo: "Asia/Tokyo",
    sydney: "Australia/Sydney"
  };

  function getLocalDateTimeValue(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    const hours = String(date.getHours()).padStart(2, "0");
    const minutes = String(date.getMinutes()).padStart(2, "0");

    return year + "-" + month + "-" + day + "T" + hours + ":" + minutes;
  }

  function formatZoneTime(date, timeZone) {
    return new Intl.DateTimeFormat("en", {
      timeZone: timeZone,
      weekday: "short",
      month: "short",
      day: "numeric",
      year: "numeric",
      hour: "2-digit",
      minute: "2-digit"
    }).format(date);
  }

  function convertTimeZones() {
    if (!input.value) return;

    const selectedDate = new Date(input.value);

    Object.keys(zones).forEach(function (key) {
      outputs[key].textContent = formatZoneTime(selectedDate, zones[key]);
    });
  }

  input.value = getLocalDateTimeValue(new Date());
  button.addEventListener("click", convertTimeZones);
  input.addEventListener("change", convertTimeZones);

  convertTimeZones();
})();

HTML

<div class="vb-date-twentyfive-demo">
  <div class="vb-date-twentyfive-card" data-vb-date-twentyfive>
    <div class="vb-date-twentyfive-form">
      <span class="vb-date-twentyfive-kicker">Example 25</span>
      <h3>Time Zone Converter</h3>
      <p>Select one date and time, then convert it into multiple international time zones.</p>

      <label>
        <span>Select date and time</span>
        <input type="datetime-local" data-timezone-input>
      </label>

      <button type="button" data-convert-timezones>Convert Time Zones</button>
    </div>

    <div class="vb-date-twentyfive-results">
      <div>
        <span>New York</span>
        <strong data-zone-newyork>--</strong>
      </div>
      <div>
        <span>London</span>
        <strong data-zone-london>--</strong>
      </div>
      <div>
        <span>Tallinn</span>
        <strong data-zone-tallinn>--</strong>
      </div>
      <div>
        <span>Tokyo</span>
        <strong data-zone-tokyo>--</strong>
      </div>
      <div>
        <span>Sydney</span>
        <strong data-zone-sydney>--</strong>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-twentyfive-demo,
.vb-date-twentyfive-demo * {
  box-sizing: border-box;
}

.vb-date-twentyfive-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #ecfeff 0%, #eef2ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentyfive-card {
  display: grid;
  grid-template-columns: minmax(280px, 0.85fr) minmax(0, 1.15fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-twentyfive-form,
.vb-date-twentyfive-results {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-twentyfive-form {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-twentyfive-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentyfive-form h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-twentyfive-form p {
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-twentyfive-form label {
  display: grid;
  gap: 8px;
  margin-bottom: 14px;
}

.vb-date-twentyfive-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-twentyfive-form input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-twentyfive-form button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #06b6d4);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-twentyfive-results {
  display: grid;
  gap: 12px;
  padding: clamp(20px, 4vw, 30px);
  background:
    radial-gradient(circle at 16% 14%, rgba(34, 211, 238, 0.20), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #1e3a8a 100%) !important;
}

.vb-date-twentyfive-results div {
  min-width: 0;
  display: grid;
  gap: 7px;
  padding: 16px;
  border-radius: 20px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentyfive-results span {
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-date-twentyfive-results strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(18px, 3vw, 26px);
  line-height: 1.25;
  font-weight: 950;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-date-twentyfive-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-twentyfive-form,
  .vb-date-twentyfive-results,
  .vb-date-twentyfive-results div {
    border-radius: 24px;
  }

  .vb-date-twentyfive-form button {
    width: 100%;
  }
}

This JavaScript time zone converter is useful for webinar pages, international booking systems, remote team dashboards, event pages, calendar tools, meeting planners, and SaaS applications with global users.

26. JavaScript Business Days Calculator

A JavaScript business days calculator is highly practical for delivery estimates, project timelines, invoice due dates, support response times, HR tools, booking systems, and admin dashboards. It calculates weekdays between two selected dates while excluding Saturdays and Sundays.

This example lets users select a start date and an end date. JavaScript calculates total calendar days, business days, and weekend days.

Example 26

Business Days Calculator

Select a start and end date to calculate business days, weekends, and total calendar days.

Business days --
Total days --
Weekend days --

Select dates to calculate the business day range.

JavaScript

(function () {
  const calculator = document.querySelector("[data-vb-date-twentysix]");
  if (!calculator) return;

  const startInput = calculator.querySelector("[data-business-start]");
  const endInput = calculator.querySelector("[data-business-end]");
  const button = calculator.querySelector("[data-calculate-business-days]");
  const error = calculator.querySelector("[data-business-error]");

  const businessOutput = calculator.querySelector("[data-business-days]");
  const totalOutput = calculator.querySelector("[data-total-days]");
  const weekendOutput = calculator.querySelector("[data-weekend-days]");
  const summary = calculator.querySelector("[data-business-summary]");

  function formatInputDate(date) {
    return date.getFullYear() + "-" +
      String(date.getMonth() + 1).padStart(2, "0") + "-" +
      String(date.getDate()).padStart(2, "0");
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function calculateBusinessDays() {
    error.textContent = "";

    if (!startInput.value || !endInput.value) {
      error.textContent = "Please select both dates.";
      return;
    }

    const startDate = createLocalDate(startInput.value);
    const endDate = createLocalDate(endInput.value);

    if (endDate < startDate) {
      error.textContent = "End date must be after the start date.";
      return;
    }

    let current = new Date(startDate);
    let totalDays = 0;
    let businessDays = 0;
    let weekendDays = 0;

    while (current <= endDate) {
      const day = current.getDay();

      totalDays += 1;

      if (day === 0 || day === 6) {
        weekendDays += 1;
      } else {
        businessDays += 1;
      }

      current.setDate(current.getDate() + 1);
    }

    businessOutput.textContent = businessDays;
    totalOutput.textContent = totalDays;
    weekendOutput.textContent = weekendDays;
    summary.textContent = "This range includes " + businessDays + " business days and " + weekendDays + " weekend days.";
  }

  function setDefaultDates() {
    const start = new Date();
    const end = new Date();

    end.setDate(start.getDate() + 14);

    startInput.value = formatInputDate(start);
    endInput.value = formatInputDate(end);

    calculateBusinessDays();
  }

  button.addEventListener("click", calculateBusinessDays);
  startInput.addEventListener("change", calculateBusinessDays);
  endInput.addEventListener("change", calculateBusinessDays);

  setDefaultDates();
})();

HTML

<div class="vb-date-twentysix-demo">
  <div class="vb-date-twentysix-card" data-vb-date-twentysix>
    <div class="vb-date-twentysix-form">
      <span class="vb-date-twentysix-kicker">Example 26</span>
      <h3>Business Days Calculator</h3>
      <p>Select a start and end date to calculate business days, weekends, and total calendar days.</p>

      <div class="vb-date-twentysix-inputs">
        <label>
          <span>Start date</span>
          <input type="date" data-business-start>
        </label>

        <label>
          <span>End date</span>
          <input type="date" data-business-end>
        </label>
      </div>

      <button type="button" data-calculate-business-days>Calculate Business Days</button>
      <small data-business-error></small>
    </div>

    <div class="vb-date-twentysix-results">
      <div class="vb-date-twentysix-main">
        <span>Business days</span>
        <strong data-business-days>--</strong>
      </div>

      <div class="vb-date-twentysix-stats">
        <div>
          <span>Total days</span>
          <strong data-total-days>--</strong>
        </div>
        <div>
          <span>Weekend days</span>
          <strong data-weekend-days>--</strong>
        </div>
      </div>

      <p data-business-summary>Select dates to calculate the business day range.</p>
    </div>
  </div>
</div>

CSS

.vb-date-twentysix-demo,
.vb-date-twentysix-demo * {
  box-sizing: border-box;
}

.vb-date-twentysix-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(34, 197, 94, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(245, 158, 11, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #fffbeb 55%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentysix-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 420px);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-twentysix-form,
.vb-date-twentysix-results {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-twentysix-form {
  display: grid;
  align-content: center;
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-twentysix-kicker {
  display: inline-flex;
  justify-self: start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentysix-form h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-twentysix-form p {
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-twentysix-inputs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 14px;
}

.vb-date-twentysix-inputs label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-twentysix-inputs span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-twentysix-inputs input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-twentysix-form button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #16a34a, #f59e0b);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-twentysix-form small {
  display: block;
  min-height: 20px;
  margin-top: 10px;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 13px;
  line-height: 1.4;
  font-weight: 750;
}

.vb-date-twentysix-results {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(20px, 4vw, 30px);
  background:
    radial-gradient(circle at 16% 14%, rgba(34, 197, 94, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #365314 100%) !important;
}

.vb-date-twentysix-main,
.vb-date-twentysix-stats div {
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentysix-main span,
.vb-date-twentysix-stats span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-date-twentysix-main strong {
  display: block;
  margin-top: 8px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(58px, 10vw, 96px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.08em;
}

.vb-date-twentysix-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-twentysix-stats strong {
  display: block;
  margin-top: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 28px;
  line-height: 1;
  font-weight: 950;
}

.vb-date-twentysix-results p {
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 900px) {
  .vb-date-twentysix-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-twentysix-inputs,
  .vb-date-twentysix-stats {
    grid-template-columns: 1fr;
  }

  .vb-date-twentysix-form,
  .vb-date-twentysix-results,
  .vb-date-twentysix-main,
  .vb-date-twentysix-stats div {
    border-radius: 24px;
  }

  .vb-date-twentysix-form button {
    width: 100%;
  }
}

This JavaScript business days calculator is useful for delivery estimates, invoice due dates, project timelines, HR tools, support response calculators, booking systems, and admin dashboards.

27. Add or Subtract Days from a Date

A JavaScript add days to date calculator is one of the most searched and practical date examples. It is useful for delivery date calculators, warranty estimators, invoice due dates, trial period calculators, subscription renewals, booking systems, and project planning tools.

This example lets users select a base date, enter a number of days, and choose whether to add or subtract days. JavaScript calculates the final date instantly.

Example 27

Add or Subtract Days

Select a base date, enter a number of days, and calculate the final date.

Final date --
ISO format --
Operation --

JavaScript

(function () {
  const calculator = document.querySelector("[data-vb-date-twentyseven]");
  if (!calculator) return;

  const baseInput = calculator.querySelector("[data-base-date]");
  const amountInput = calculator.querySelector("[data-day-amount]");
  const addButton = calculator.querySelector("[data-add-days]");
  const subtractButton = calculator.querySelector("[data-subtract-days]");

  const readableOutput = calculator.querySelector("[data-final-readable]");
  const isoOutput = calculator.querySelector("[data-final-iso]");
  const operationOutput = calculator.querySelector("[data-final-operation]");

  function formatInputDate(date) {
    return date.getFullYear() + "-" +
      String(date.getMonth() + 1).padStart(2, "0") + "-" +
      String(date.getDate()).padStart(2, "0");
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function formatReadableDate(date) {
    return new Intl.DateTimeFormat("en", {
      weekday: "long",
      month: "long",
      day: "numeric",
      year: "numeric"
    }).format(date);
  }

  function calculateDate(direction) {
    if (!baseInput.value) return;

    const amount = Math.max(0, Number(amountInput.value || 0));
    const baseDate = createLocalDate(baseInput.value);
    const resultDate = new Date(baseDate);

    if (direction === "add") {
      resultDate.setDate(resultDate.getDate() + amount);
      operationOutput.textContent = "+" + amount + " days";
    } else {
      resultDate.setDate(resultDate.getDate() - amount);
      operationOutput.textContent = "-" + amount + " days";
    }

    readableOutput.textContent = formatReadableDate(resultDate);
    isoOutput.textContent = formatInputDate(resultDate);
  }

  baseInput.value = formatInputDate(new Date());

  addButton.addEventListener("click", function () {
    calculateDate("add");
  });

  subtractButton.addEventListener("click", function () {
    calculateDate("subtract");
  });

  baseInput.addEventListener("change", function () {
    calculateDate("add");
  });

  amountInput.addEventListener("input", function () {
    calculateDate("add");
  });

  calculateDate("add");
})();

HTML

<div class="vb-date-twentyseven-demo">
  <div class="vb-date-twentyseven-card" data-vb-date-twentyseven>
    <div class="vb-date-twentyseven-form">
      <span class="vb-date-twentyseven-kicker">Example 27</span>
      <h3>Add or Subtract Days</h3>
      <p>Select a base date, enter a number of days, and calculate the final date.</p>

      <label>
        <span>Base date</span>
        <input type="date" data-base-date>
      </label>

      <label>
        <span>Number of days</span>
        <input type="number" min="0" value="30" data-day-amount>
      </label>

      <div class="vb-date-twentyseven-actions">
        <button type="button" data-add-days>Add Days</button>
        <button type="button" data-subtract-days>Subtract Days</button>
      </div>
    </div>

    <div class="vb-date-twentyseven-result">
      <div class="vb-date-twentyseven-main">
        <span>Final date</span>
        <strong data-final-readable>--</strong>
      </div>

      <div class="vb-date-twentyseven-mini">
        <div>
          <span>ISO format</span>
          <strong data-final-iso>--</strong>
        </div>
        <div>
          <span>Operation</span>
          <strong data-final-operation>--</strong>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-twentyseven-demo,
.vb-date-twentyseven-demo * {
  box-sizing: border-box;
}

.vb-date-twentyseven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(236, 72, 153, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(124, 58, 237, 0.16), transparent 34%),
    linear-gradient(135deg, #fdf2f8 0%, #f5f3ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(236, 72, 153, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentyseven-card {
  display: grid;
  grid-template-columns: minmax(280px, 0.85fr) minmax(0, 1.15fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-twentyseven-form,
.vb-date-twentyseven-result {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-twentyseven-form {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-twentyseven-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #fce7f3;
  color: #be185d !important;
  -webkit-text-fill-color: #be185d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentyseven-form h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-twentyseven-form p {
  margin: 0 0 8px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-twentyseven-form label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-twentyseven-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-twentyseven-form input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-twentyseven-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.vb-date-twentyseven-actions button {
  flex: 1;
  min-width: 140px;
  min-height: 48px;
  padding: 12px 16px;
  border: 0;
  border-radius: 999px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-twentyseven-actions button:first-child {
  background: linear-gradient(135deg, #db2777, #7c3aed);
}

.vb-date-twentyseven-actions button:last-child {
  background: linear-gradient(135deg, #0f172a, #475569);
}

.vb-date-twentyseven-result {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(20px, 4vw, 30px);
  background:
    radial-gradient(circle at 16% 14%, rgba(236, 72, 153, 0.22), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #831843 100%) !important;
}

.vb-date-twentyseven-main,
.vb-date-twentyseven-mini div {
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentyseven-main span,
.vb-date-twentyseven-mini span {
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-date-twentyseven-main strong {
  display: block;
  margin-top: 8px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 5vw, 54px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-date-twentyseven-mini {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-twentyseven-mini strong {
  display: block;
  margin-top: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  line-height: 1.25;
  font-weight: 950;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-date-twentyseven-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-twentyseven-form,
  .vb-date-twentyseven-result,
  .vb-date-twentyseven-main,
  .vb-date-twentyseven-mini div {
    border-radius: 24px;
  }

  .vb-date-twentyseven-actions,
  .vb-date-twentyseven-mini {
    grid-template-columns: 1fr;
    display: grid;
  }

  .vb-date-twentyseven-actions button {
    width: 100%;
    min-width: 0;
  }
}

This JavaScript add or subtract days calculator is useful for delivery date calculators, invoice due date tools, warranty calculators, subscription renewal dates, booking systems, trial period calculators, and project planning dashboards.

28. JavaScript Age Calculator from Date of Birth

A JavaScript age calculator is one of the most practical date examples for forms, registration pages, profile tools, healthcare forms, insurance calculators, HR systems, and user dashboards. It calculates a user’s exact age from their date of birth.

This example lets users select a birth date and calculates years, months, and days. JavaScript also validates that the selected date is not in the future.

Example 28

Age Calculator

Select a date of birth and JavaScript calculates the exact age in years, months, and days.

Calculated age --
Years --
Months --
Days --

JavaScript

(function () {
  const calculator = document.querySelector("[data-vb-date-twentyeight]");
  if (!calculator) return;

  const birthInput = calculator.querySelector("[data-birth-date]");
  const button = calculator.querySelector("[data-calculate-age]");
  const error = calculator.querySelector("[data-age-error]");

  const summary = calculator.querySelector("[data-age-summary]");
  const yearsOutput = calculator.querySelector("[data-age-years]");
  const monthsOutput = calculator.querySelector("[data-age-months]");
  const daysOutput = calculator.querySelector("[data-age-days]");

  function formatInputDate(date) {
    return date.getFullYear() + "-" +
      String(date.getMonth() + 1).padStart(2, "0") + "-" +
      String(date.getDate()).padStart(2, "0");
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function calculateAge() {
    error.textContent = "";

    if (!birthInput.value) {
      error.textContent = "Please select a date of birth.";
      return;
    }

    const birthDate = createLocalDate(birthInput.value);
    const today = new Date();

    birthDate.setHours(0, 0, 0, 0);
    today.setHours(0, 0, 0, 0);

    if (birthDate > today) {
      error.textContent = "Birth date cannot be in the future.";
      return;
    }

    let years = today.getFullYear() - birthDate.getFullYear();
    let months = today.getMonth() - birthDate.getMonth();
    let days = today.getDate() - birthDate.getDate();

    if (days < 0) {
      months -= 1;
      const previousMonth = new Date(today.getFullYear(), today.getMonth(), 0);
      days += previousMonth.getDate();
    }

    if (months < 0) {
      years -= 1;
      months += 12;
    }

    yearsOutput.textContent = years;
    monthsOutput.textContent = months;
    daysOutput.textContent = days;
    summary.textContent = years + " years, " + months + " months, " + days + " days";
  }

  const defaultDate = new Date();
  defaultDate.setFullYear(defaultDate.getFullYear() - 25);
  birthInput.value = formatInputDate(defaultDate);

  button.addEventListener("click", calculateAge);
  birthInput.addEventListener("change", calculateAge);

  calculateAge();
})();

HTML

<div class="vb-date-twentyeight-demo">
  <div class="vb-date-twentyeight-card" data-vb-date-twentyeight>
    <div class="vb-date-twentyeight-form">
      <span class="vb-date-twentyeight-kicker">Example 28</span>
      <h3>Age Calculator</h3>
      <p>Select a date of birth and JavaScript calculates the exact age in years, months, and days.</p>

      <label>
        <span>Date of birth</span>
        <input type="date" data-birth-date>
      </label>

      <button type="button" data-calculate-age>Calculate Age</button>
      <small data-age-error></small>
    </div>

    <div class="vb-date-twentyeight-results">
      <div class="vb-date-twentyeight-main">
        <span>Calculated age</span>
        <strong data-age-summary>--</strong>
      </div>

      <div class="vb-date-twentyeight-grid">
        <div>
          <span>Years</span>
          <strong data-age-years>--</strong>
        </div>
        <div>
          <span>Months</span>
          <strong data-age-months>--</strong>
        </div>
        <div>
          <span>Days</span>
          <strong data-age-days>--</strong>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-date-twentyeight-demo,
.vb-date-twentyeight-demo * {
  box-sizing: border-box;
}

.vb-date-twentyeight-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(59, 130, 246, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #f0fdf4 55%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentyeight-card {
  display: grid;
  grid-template-columns: minmax(280px, 0.85fr) minmax(0, 1.15fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-twentyeight-form,
.vb-date-twentyeight-results {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-twentyeight-form {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-twentyeight-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentyeight-form h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-twentyeight-form p {
  margin: 0 0 8px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-twentyeight-form label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-twentyeight-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-twentyeight-form input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-twentyeight-form button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #16a34a);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-twentyeight-form small {
  min-height: 20px;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 13px;
  line-height: 1.4;
  font-weight: 750;
}

.vb-date-twentyeight-results {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(20px, 4vw, 30px);
  background:
    radial-gradient(circle at 16% 14%, rgba(34, 197, 94, 0.20), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #14532d 100%) !important;
}

.vb-date-twentyeight-main,
.vb-date-twentyeight-grid div {
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentyeight-main span,
.vb-date-twentyeight-grid span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-date-twentyeight-main strong {
  display: block;
  margin-top: 8px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 5vw, 54px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-date-twentyeight-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-twentyeight-grid strong {
  display: block;
  margin-top: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 32px;
  line-height: 1;
  font-weight: 950;
}

@media (max-width: 900px) {
  .vb-date-twentyeight-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-twentyeight-form,
  .vb-date-twentyeight-results,
  .vb-date-twentyeight-main,
  .vb-date-twentyeight-grid div {
    border-radius: 24px;
  }

  .vb-date-twentyeight-grid {
    grid-template-columns: 1fr;
  }

  .vb-date-twentyeight-form button {
    width: 100%;
  }
}

This JavaScript age calculator is useful for signup forms, profile pages, HR tools, insurance calculators, healthcare forms, registration pages, user dashboards, and date of birth validation examples.

29. JavaScript Relative Time Formatter

A JavaScript relative time formatter is a highly practical date and time example for blogs, comments, notifications, dashboards, activity feeds, chat apps, and social interfaces. It turns dates into readable labels like “5 minutes ago” or “in 2 days”.

This example lets users select a date and time, then JavaScript calculates whether that time is in the past or future and formats it as a human-friendly relative time label.

Example 29

Relative Time Formatter

Select a date and time to convert it into a human-friendly relative time label.

Relative time --

Select a date and time to generate a relative time label.

JavaScript

(function () {
  const formatter = document.querySelector("[data-vb-date-twentynine]");
  if (!formatter) return;

  const input = formatter.querySelector("[data-relative-input]");
  const button = formatter.querySelector("[data-format-relative]");
  const label = formatter.querySelector("[data-relative-label]");
  const details = formatter.querySelector("[data-relative-details]");

  function getLocalDateTimeValue(date) {
    return date.getFullYear() + "-" +
      String(date.getMonth() + 1).padStart(2, "0") + "-" +
      String(date.getDate()).padStart(2, "0") + "T" +
      String(date.getHours()).padStart(2, "0") + ":" +
      String(date.getMinutes()).padStart(2, "0");
  }

  function getRelativeTime(date) {
    const now = new Date();
    const diffSeconds = Math.round((date - now) / 1000);
    const absSeconds = Math.abs(diffSeconds);

    const units = [
      { name: "year", seconds: 31536000 },
      { name: "month", seconds: 2592000 },
      { name: "week", seconds: 604800 },
      { name: "day", seconds: 86400 },
      { name: "hour", seconds: 3600 },
      { name: "minute", seconds: 60 },
      { name: "second", seconds: 1 }
    ];

    for (let i = 0; i < units.length; i += 1) {
      const unit = units[i];

      if (absSeconds >= unit.seconds || unit.name === "second") {
        const value = Math.round(diffSeconds / unit.seconds);
        return new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(value, unit.name);
      }
    }
  }

  function formatRelativeTime() {
    if (!input.value) return;

    const selectedDate = new Date(input.value);
    const readableDate = new Intl.DateTimeFormat("en", {
      month: "long",
      day: "numeric",
      year: "numeric",
      hour: "2-digit",
      minute: "2-digit"
    }).format(selectedDate);

    label.textContent = getRelativeTime(selectedDate);
    details.textContent = "Selected date: " + readableDate;
  }

  const defaultDate = new Date();
  defaultDate.setHours(defaultDate.getHours() - 3);

  input.value = getLocalDateTimeValue(defaultDate);

  button.addEventListener("click", formatRelativeTime);
  input.addEventListener("change", formatRelativeTime);

  formatRelativeTime();
})();

HTML

<div class="vb-date-twentynine-demo">
  <div class="vb-date-twentynine-card" data-vb-date-twentynine>
    <div class="vb-date-twentynine-form">
      <span class="vb-date-twentynine-kicker">Example 29</span>
      <h3>Relative Time Formatter</h3>
      <p>Select a date and time to convert it into a human-friendly relative time label.</p>

      <label>
        <span>Date and time</span>
        <input type="datetime-local" data-relative-input>
      </label>

      <button type="button" data-format-relative>Format Relative Time</button>
    </div>

    <div class="vb-date-twentynine-output">
      <div>
        <span>Relative time</span>
        <strong data-relative-label>--</strong>
      </div>

      <p data-relative-details>Select a date and time to generate a relative time label.</p>
    </div>
  </div>
</div>

CSS

.vb-date-twentynine-demo,
.vb-date-twentynine-demo * {
  box-sizing: border-box;
}

.vb-date-twentynine-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(14, 165, 233, 0.16), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #ecfeff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-twentynine-card {
  display: grid;
  grid-template-columns: minmax(280px, 0.9fr) minmax(0, 1.1fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-twentynine-form,
.vb-date-twentynine-output {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-twentynine-form {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-twentynine-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-twentynine-form h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-twentynine-form p {
  margin: 0 0 8px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-twentynine-form label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-twentynine-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-twentynine-form input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-twentynine-form button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #7c3aed, #06b6d4);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-twentynine-output {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(20px, 4vw, 30px);
  background:
    radial-gradient(circle at 16% 14%, rgba(168, 85, 247, 0.22), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #312e81 100%) !important;
}

.vb-date-twentynine-output div {
  min-width: 0;
  padding: 22px;
  border-radius: 24px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-twentynine-output span {
  color: #c4b5fd !important;
  -webkit-text-fill-color: #c4b5fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-date-twentynine-output strong {
  display: block;
  margin-top: 8px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 66px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.065em;
  overflow-wrap: anywhere;
}

.vb-date-twentynine-output p {
  margin: 0 !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 900px) {
  .vb-date-twentynine-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-twentynine-form,
  .vb-date-twentynine-output,
  .vb-date-twentynine-output div {
    border-radius: 24px;
  }

  .vb-date-twentynine-form button {
    width: 100%;
  }
}

This JavaScript relative time formatter is useful for blogs, comment sections, notification systems, activity feeds, chat interfaces, admin dashboards, social UI components, and content timestamps.

30. JavaScript Week Number Calculator

A JavaScript week number calculator is useful for project planning, work schedules, reporting dashboards, booking systems, content calendars, payroll tools, logistics tools, and business applications. It calculates the ISO week number for a selected date.

This example lets users select any date and JavaScript calculates the ISO week number, year, weekday, and readable date label.

Example 30

Week Number Calculator

Select a date and JavaScript calculates the ISO week number and related date details.

ISO week number --
ISO year --
Weekday --

Select a date to calculate its week number.

JavaScript

(function () {
  const calculator = document.querySelector("[data-vb-date-thirty]");
  if (!calculator) return;

  const input = calculator.querySelector("[data-week-date]");
  const button = calculator.querySelector("[data-calculate-week]");

  const weekNumber = calculator.querySelector("[data-week-number]");
  const weekYear = calculator.querySelector("[data-week-year]");
  const weekdayName = calculator.querySelector("[data-weekday-name]");
  const summary = calculator.querySelector("[data-week-summary]");

  function formatInputDate(date) {
    return date.getFullYear() + "-" +
      String(date.getMonth() + 1).padStart(2, "0") + "-" +
      String(date.getDate()).padStart(2, "0");
  }

  function createLocalDate(value) {
    const parts = value.split("-");
    return new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
  }

  function getISOWeekData(date) {
    const target = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
    const dayNumber = target.getUTCDay() || 7;

    target.setUTCDate(target.getUTCDate() + 4 - dayNumber);

    const isoYear = target.getUTCFullYear();
    const yearStart = new Date(Date.UTC(isoYear, 0, 1));
    const isoWeek = Math.ceil((((target - yearStart) / 86400000) + 1) / 7);

    return {
      week: isoWeek,
      year: isoYear
    };
  }

  function calculateWeekNumber() {
    if (!input.value) return;

    const selectedDate = createLocalDate(input.value);
    const weekData = getISOWeekData(selectedDate);

    weekNumber.textContent = weekData.week;
    weekYear.textContent = weekData.year;

    weekdayName.textContent = new Intl.DateTimeFormat("en", {
      weekday: "long"
    }).format(selectedDate);

    summary.textContent = "The selected date is in ISO week " + weekData.week + " of " + weekData.year + ".";
  }

  input.value = formatInputDate(new Date());

  button.addEventListener("click", calculateWeekNumber);
  input.addEventListener("change", calculateWeekNumber);

  calculateWeekNumber();
})();

HTML

<div class="vb-date-thirty-demo">
  <div class="vb-date-thirty-card" data-vb-date-thirty>
    <div class="vb-date-thirty-form">
      <span class="vb-date-thirty-kicker">Example 30</span>
      <h3>Week Number Calculator</h3>
      <p>Select a date and JavaScript calculates the ISO week number and related date details.</p>

      <label>
        <span>Select date</span>
        <input type="date" data-week-date>
      </label>

      <button type="button" data-calculate-week>Calculate Week Number</button>
    </div>

    <div class="vb-date-thirty-results">
      <div class="vb-date-thirty-main">
        <span>ISO week number</span>
        <strong data-week-number>--</strong>
      </div>

      <div class="vb-date-thirty-grid">
        <div>
          <span>ISO year</span>
          <strong data-week-year>--</strong>
        </div>
        <div>
          <span>Weekday</span>
          <strong data-weekday-name>--</strong>
        </div>
      </div>

      <p data-week-summary>Select a date to calculate its week number.</p>
    </div>
  </div>
</div>

CSS

.vb-date-thirty-demo,
.vb-date-thirty-demo * {
  box-sizing: border-box;
}

.vb-date-thirty-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 12% 18%, rgba(245, 158, 11, 0.16), transparent 34%),
    radial-gradient(circle at 88% 18%, rgba(239, 68, 68, 0.14), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #fef2f2 55%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-date-thirty-card {
  display: grid;
  grid-template-columns: minmax(280px, 0.85fr) minmax(0, 1.15fr);
  gap: 20px;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-date-thirty-form,
.vb-date-thirty-results {
  min-width: 0;
  border-radius: 34px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 76px rgba(15, 23, 42, 0.10);
}

.vb-date-thirty-form {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(22px, 4vw, 38px);
  background: #ffffff;
}

.vb-date-thirty-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #fef3c7;
  color: #b45309 !important;
  -webkit-text-fill-color: #b45309 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-date-thirty-form h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-date-thirty-form p {
  margin: 0 0 8px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-date-thirty-form label {
  display: grid;
  gap: 8px;
  min-width: 0;
}

.vb-date-thirty-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-date-thirty-form input {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.36);
  border-radius: 17px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-date-thirty-form button {
  min-height: 48px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #f59e0b, #ef4444);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-date-thirty-results {
  display: grid;
  align-content: center;
  gap: 14px;
  padding: clamp(20px, 4vw, 30px);
  background:
    radial-gradient(circle at 16% 14%, rgba(251, 191, 36, 0.20), transparent 34%),
    linear-gradient(135deg, #111827 0%, #7c2d12 100%) !important;
}

.vb-date-thirty-main,
.vb-date-thirty-grid div {
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-date-thirty-main span,
.vb-date-thirty-grid span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-date-thirty-main strong {
  display: block;
  margin-top: 8px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(70px, 12vw, 120px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.08em;
}

.vb-date-thirty-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-date-thirty-grid strong {
  display: block;
  margin-top: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  line-height: 1.15;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-date-thirty-results p {
  margin: 0 !important;
  color: #ffedd5 !important;
  -webkit-text-fill-color: #ffedd5 !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 900px) {
  .vb-date-thirty-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-date-thirty-form,
  .vb-date-thirty-results,
  .vb-date-thirty-main,
  .vb-date-thirty-grid div {
    border-radius: 24px;
  }

  .vb-date-thirty-grid {
    grid-template-columns: 1fr;
  }

  .vb-date-thirty-form button {
    width: 100%;
  }
}

This JavaScript week number calculator is useful for reporting dashboards, payroll tools, logistics systems, booking calendars, project planning pages, content calendars, work schedules, and business applications.

EntitySchema Pro WordPress structured data and entity SEO plugin
WordPress SEO Plugin

EntitySchema Pro

Build entity-based structured data for modern WordPress SEO, AI-readable content, schema markup, and stronger search visibility.

29€ 89€ Save 60€
View Plugin

JavaScript Date and Time Best Practices

JavaScript date and time examples can look simple, but real website features need careful planning. A good date component should display clear information, handle invalid input, work across devices, and avoid confusing users with unclear date formats or broken countdown states.

The most important rule is to make the result understandable. Users should not need to guess whether a date means month/day/year or day/month/year, whether a countdown is based on their local time, whether a booking date is valid, or why a deadline has already expired.

For simple website features, vanilla JavaScript is often enough. You can create date formatters, countdowns, delivery date calculators, business day tools, age calculators, relative time labels, timestamp converters, and week number calculators without a large framework. For larger booking systems or international scheduling tools, server-side validation and timezone-safe backend logic may also be needed.

Responsive Date and Time UI Tips

Date and time components often include big numbers, multiple cards, form inputs, buttons, countdown blocks, and result panels. These layouts can look good on desktop but break quickly on mobile if the grid is not planned correctly.

Use flexible grids

Countdown cards, date result panels, and calculator sections should collapse into one column on smaller screens.

Prevent text overflow

Long timezone names, formatted dates, and relative time labels should use wrapping rules so they stay inside the card.

Keep inputs full width

Date inputs, datetime fields, select boxes, and number fields are easier to use on mobile when they stretch to the card width.

Reduce oversized numbers

Large countdown digits and week numbers should use responsive font sizes with clamp() instead of fixed desktop sizes.

Responsive design is especially important for date pickers, calculators, and dashboards because users often interact with them from mobile devices. A delivery date calculator, booking form, age calculator, or countdown timer should remain readable and usable even when the screen is narrow.

Common JavaScript Date and Time Mistakes

Many JavaScript date problems come from small assumptions. A developer may assume every month has the same number of days, forget that JavaScript months are zero-based, show unclear numeric formats, or build a countdown that breaks when the date passes.

These mistakes can make forms, booking systems, dashboards, ecommerce pages, and countdown sections feel unreliable. Always test realistic user scenarios before publishing a date or time feature.

A professional JavaScript date component should be predictable. If the selected date is invalid, show a clear message. If a deadline has passed, show a complete state. If the output depends on local time, make that clear. If the feature is part of a real booking, payment, legal, or business system, validate the final result on the server too.

JavaScript Date and Time FAQ

Here are common questions about building JavaScript date and time components for websites, dashboards, ecommerce pages, forms, calculators, booking tools, and practical UI examples.

What is the JavaScript Date object?

The JavaScript Date object is the built-in browser object used to create, read, compare, and format dates and times. It can represent the current moment, a selected user date, a future deadline, or a custom timestamp.

How do I format a date in JavaScript?

You can format dates with Intl.DateTimeFormat, manual helper functions, or methods like toLocaleDateString(). For readable website UI, Intl.DateTimeFormat is usually cleaner because it supports weekdays, month names, years, hours, minutes, and locale-aware formatting.

How do I calculate the difference between two dates?

Convert both dates to milliseconds by subtracting one Date object from another. Then divide the difference by the number of milliseconds in a day, hour, or minute depending on what you need to calculate.

How do I create a JavaScript countdown timer?

Create a target date, compare it with the current date, calculate the remaining milliseconds, convert that value into days, hours, minutes, and seconds, then update the HTML with setInterval().

How can I stop a countdown from breaking when it expires?

Check whether the remaining time is less than or equal to zero. Then show an expired state, complete message, hidden button, or a new dynamic target date instead of allowing negative values to appear.

How do I validate a date input with JavaScript?

Read the input value, convert it into a local Date object, then compare it with your minimum date, maximum date, current date, or other business rules. Always show a clear error message when the selected date is not allowed.

How do I calculate business days in JavaScript?

Loop through the date range one day at a time and count only days where getDay() is not Saturday or Sunday. For real business systems, you may also need to exclude holidays from a custom list.

Should I handle time zones in JavaScript?

JavaScript can display time in different time zones using Intl.DateTimeFormat with a timeZone option. For serious booking, payment, or scheduling systems, store important times on the server in a reliable format and use JavaScript mainly for display.

Why do JavaScript months start at 0?

When creating dates with new Date(year, month, day), JavaScript uses zero-based months. January is 0, February is 1, and December is 11. This is one of the most common beginner mistakes in JavaScript date examples.

Can I use these JavaScript date examples in WordPress?

Yes. These examples are written as vanilla JavaScript, HTML, and CSS, so they can be placed in a Gutenberg Custom HTML block, a custom theme template, a plugin, or a static HTML page. For WordPress posts, keep class names unique so examples do not conflict with theme styles.

Conclusion

JavaScript date and time features are some of the most useful small tools you can add to a website. They help users understand deadlines, compare dates, calculate delivery times, validate forms, check business days, convert timestamps, display time zones, and interact with dashboards, booking systems, ecommerce pages, event pages, and productivity tools.

The 30 examples in this guide show how flexible vanilla JavaScript can be. You can build live clocks, countdowns, date formatters, age calculators, booking validators, business day calculators, timestamp converters, timezone displays, relative time labels, and week number tools with simple HTML, CSS, and JavaScript.

For best results, keep every date component clear and practical. Use readable formats, handle invalid input, avoid broken expired states, test mobile layouts, and make sure the user understands exactly what the date or time result means. When a feature affects real bookings, payments, schedules, or business rules, combine the front-end JavaScript with reliable server-side validation.

Continue with these related guides if you want more practical JavaScript examples, UI components, calculators, form logic, saved browser state, and responsive front-end patterns.

Meta titles and meta descriptions are small SEO elements, but they can have a big impact on how your pages appear in search results. A good SEO snippet helps searchers understand what the page offers, why it matches their intent, and why they should click your result instead of another one.

This guide gives you 30 professional AI prompts for meta titles and meta descriptions. These prompts are built for real SEO workflows: writing SEO titles, improving click-through rate, matching search intent, creating meta descriptions for blog posts, landing pages, ecommerce pages, service pages, local SEO pages, WordPress posts, and refreshing old snippets that are too long, too generic, or not aligned with the page content.

This post focuses only on SEO snippets and page-level optimization. It does not replace keyword research or full content writing. For those parts of the workflow, use our related guides: ChatGPT prompts for SEO content writing, ChatGPT prompts for keyword research, AI prompts for website copywriting, and ChatGPT prompts for WordPress websites.

View All 30 AI Meta Title and Description Prompts

What Are AI Prompts for Meta Titles and Descriptions?

AI prompts for meta titles and meta descriptions are structured instructions that help tools like ChatGPT create better search snippets. Instead of asking AI to “write a meta description,” a professional prompt gives it the page type, target keyword, search intent, audience, value proposition, character or pixel limits, and the reason someone should click.

A strong SEO snippet does two jobs. First, it helps search engines and searchers understand what the page is about. Second, it helps the searcher decide whether your result is the best match for their problem. That means meta titles and descriptions should be clear, relevant, specific, and aligned with the page content.

These prompts are useful for SEO writers, WordPress site owners, bloggers, agencies, ecommerce stores, SaaS teams, local businesses, affiliate websites, and anyone who needs to improve page snippets without writing generic titles and descriptions manually for every URL.

Why SEO Snippet Prompts Matter

SEO snippet prompts matter because a weak title or description can reduce clicks even when the page has good content. If the snippet is too long, too vague, keyword-stuffed, or not aligned with search intent, users may skip it and choose another result.

How to Use These Meta Title and Description Prompts

Each prompt below is copy-paste ready. Replace the bracketed parts such as [page topic], [primary keyword], [search intent], [audience], [page URL], and [page content]. Use the output as a draft, then edit it manually so it fits your brand, page content, and SEO plugin limits.

Use these prompts after keyword research and before publishing the page. If you are still choosing keywords, start with ChatGPT prompts for keyword research. If you need the full article or page content first, use ChatGPT prompts for SEO content writing.

What Should a Good Meta Prompt Include?

Page type

Tell AI whether the page is a blog post, product page, category page, service page, landing page, or homepage.

Search intent

Explain whether the searcher wants information, examples, a tutorial, comparison, product details, or a service provider.

Main keyword

Give the primary keyword and supporting terms, but tell AI to use them naturally instead of stuffing them.

Click reason

Ask AI to include a useful benefit, page promise, or reason the result is worth clicking.

30 AI Prompts for Meta Titles and Meta Descriptions

Below are 30 professional AI prompts for meta titles and meta descriptions. Each prompt is built for one specific SEO snippet task, from writing titles for new posts to improving old descriptions, creating ecommerce snippets, and checking whether your SEO title is too long or too vague.

1. SEO Meta Title and Description Prompt

This prompt creates a complete SEO title and meta description for any page. It is best used after the page content and primary keyword are already clear.

Best forGeneral SEO snippets
OutputTitle and description options
Use whenYou need a strong starting point
Copy-paste prompt

How to customize it: Add your real page promise. This helps AI write a snippet that is not only keyword-focused, but also click-worthy.

2. Blog Post Meta Snippet Prompt

This prompt is designed for blog posts and informational articles. It helps create snippets that describe the article clearly and promise useful value.

Best forBlog posts
OutputBlog SEO titles and descriptions
Use whenYou publish informational content
Copy-paste prompt

Why it works: It gives several snippet angles, so you can choose between safe, concise, and stronger CTR-focused versions.

3. Search Intent Meta Description Prompt

This prompt focuses on matching the snippet to the reason people are searching. It is useful when your title is good but your description feels generic.

Best forSearch intent alignment
OutputIntent-matched descriptions
Use whenThe snippet must feel relevant
Copy-paste prompt

How to use it: Use this when you know the keyword but need the snippet to better match what the searcher actually wants.

4. CTR Improvement Prompt

This prompt improves existing snippets that are ranking but not getting enough clicks.

Best forImproving click-through rate
OutputCTR-focused snippet variations
Use whenYou have impressions but low clicks
Copy-paste prompt

Why it works: It focuses on improving an existing snippet instead of writing from scratch, which is useful for pages already getting impressions.

5. Short SEO Title Prompt

This prompt creates shorter SEO titles that avoid being cut off in Google results or SEO plugins.

Best forShorter SEO titles
OutputConcise title options
Use whenYour title is too long
Copy-paste prompt

How to customize it: Paste your current title from your SEO plugin. This is especially useful when the title is too wide in pixels.

6. Meta Description Shortening Prompt

This prompt shortens long meta descriptions while keeping the strongest value and main keyword.

Best forLong descriptions
OutputShorter meta descriptions
Use whenYour description is too long
Copy-paste prompt

Why it works: It keeps the snippet focused and prevents the description from becoming a cut-off first paragraph.

Need better SEO snippets for your WordPress pages?

If your titles and meta descriptions are too long, too generic, or not connected to search intent, we can help optimize your WordPress pages, blog posts, service pages, and product snippets.

Contact Us

7. WordPress SEO Snippet Prompt

This prompt is built for WordPress posts and pages where you need SEO plugin-ready titles and descriptions.

Best forWordPress SEO
OutputPlugin-ready snippets
Use whenYou publish in WordPress
Copy-paste prompt

How to use it: This works well with SEO plugins when you need the title, description, keywords, and social snippet in one workflow.

8. Ecommerce Product Meta Prompt

This prompt creates snippets for ecommerce product pages, where the searcher may already be close to buying.

Best forProduct pages
OutputProduct SEO snippets
Use whenYou sell products online
Copy-paste prompt

Why it works: Product snippets need to show relevance and buyer value without sounding like spam.

9. Ecommerce Category Meta Prompt

This prompt helps write snippets for product category pages, where users are comparing multiple products or browsing options.

Best forCategory pages
OutputCategory SEO snippets
Use whenYou optimize store categories
Copy-paste prompt

How to use it: Use this for WooCommerce categories, Shopify collections, and ecommerce archive pages.

10. Service Page Meta Prompt

This prompt creates snippets for service pages where the goal is usually lead generation or quote requests.

Best forService pages
OutputLead-focused snippets
Use whenYou sell a service
Copy-paste prompt

Why it works: Service snippets must be clear, local where needed, and focused on trust and action.

11. Local SEO Meta Prompt

This prompt creates snippets for local pages where location, service, and trust matter.

Best forLocal SEO pages
OutputLocation-based snippets
Use whenYou target a city or area
Copy-paste prompt

How to customize it: Add real trust signals only. Do not let AI invent reviews, years of experience, or guarantees.

12. Landing Page Meta Prompt

This prompt creates snippets for landing pages where the goal is conversions, not only traffic.

Best forLanding pages
OutputConversion-focused snippets
Use whenYou promote one offer
Copy-paste prompt

Why it works: Landing page snippets need a stronger value proposition, but they must still match what the searcher expects.

Want SEO snippets that support clicks and conversions?

We can help optimize meta titles, meta descriptions, landing page snippets, service page SEO, WooCommerce snippets, and WordPress SEO settings so your pages look better in search results.

Improve My SEO Snippets

13. Homepage Meta Prompt

This prompt creates homepage snippets that explain the brand, main service, and value clearly.

Best forHomepages
OutputBrand and service snippets
Use whenYour homepage snippet is vague
Copy-paste prompt

How to use it: Use this when your homepage title is only the brand name or when the description does not explain what the business actually does.

14. SaaS Meta Snippet Prompt

This prompt creates SEO snippets for SaaS pages, feature pages, use-case pages, and software landing pages.

Best forSaaS pages
OutputSoftware SEO snippets
Use whenYou sell software or tools
Copy-paste prompt

Why it works: SaaS snippets need to connect features with outcomes without becoming vague or overhyped.

15. Comparison Page Meta Prompt

This prompt creates snippets for comparison pages, alternatives articles, and versus content.

Best forComparison pages
OutputVersus snippet options
Use whenReaders compare choices
Copy-paste prompt

How to use it: Use this for “best,” “vs,” “alternatives,” and comparison articles where readers need help choosing.

16. FAQ Page Meta Prompt

This prompt creates snippets for FAQ pages, help pages, and support content.

Best forFAQ and support pages
OutputSupport-focused snippets
Use whenUsers need quick answers
Copy-paste prompt

Why it works: FAQ snippets should be clear and useful, not just stuffed with question keywords.

17. Meta Snippet Audit Prompt

This prompt reviews existing meta titles and descriptions and explains what should be fixed.

Best forSnippet auditing
OutputProblems and fixes
Use whenYou update old pages
Copy-paste prompt

How to use it: Use this for old posts, pages with weak CTR, or pages where your SEO plugin shows title or description length problems.

18. Bulk Meta Snippet Prompt

This prompt helps create meta titles and descriptions for multiple pages in one workflow.

Best forBulk optimization
OutputMeta table for many pages
Use whenYou update many URLs
Copy-paste prompt

Why it works: Bulk snippets can become repetitive. This prompt asks AI to keep each page specific and flag weak inputs.

Need to optimize many WordPress SEO snippets at once?

If you have many posts, products, or service pages with weak meta titles and descriptions, we can help build a clean metadata workflow and optimize them properly.

Optimize My Metadata

19. Meta Title Variant Testing Prompt

This prompt creates title variants for testing different angles without changing the page topic.

Best forTitle testing
OutputAngle-based title variants
Use whenYou want better CTR
Copy-paste prompt

How to use it: Use one variant, monitor performance, then test another later if impressions are good but clicks are weak.

20. Meta Description Value Proposition Prompt

This prompt focuses on making the description explain why the page is worth clicking.

Best forStronger click reason
OutputBenefit-focused descriptions
Use whenYour description is flat
Copy-paste prompt

Why it works: A meta description should not just summarize the page. It should explain why the result is useful.

21. Brand-Safe Meta Prompt

This prompt creates snippets that match a brand voice instead of sounding like generic AI SEO text.

Best forBrand voice
OutputBrand-aligned snippets
Use whenAI snippets sound generic
Copy-paste prompt

How to customize it: Add words your brand never uses. This helps prevent snippets that feel off-brand.

22. Meta Snippet Rewrite Prompt

This prompt rewrites weak snippets while preserving the original keyword and page meaning.

Best forRewriting weak snippets
OutputImproved title and description
Use whenThe current snippet is poor
Copy-paste prompt

Why it works: It improves existing text without changing the target of the page.

23. Meta Snippet From Full Content Prompt

This prompt creates snippets by analyzing the full page content instead of only a title or topic.

Best forContent-based snippets
OutputAccurate snippet options
Use whenYou have the full page ready
Copy-paste prompt

How to use it: This is one of the safest prompts because it asks AI to read the page before writing the snippet.

24. Search Console Snippet Improvement Prompt

This prompt uses Search Console performance data to improve snippets for pages with impressions but weak CTR.

Best forGSC-based optimization
OutputCTR improvement plan
Use whenYou have performance data
Copy-paste prompt

Why it works: It uses real query data instead of guessing what people search for.

Want better CTR from existing Google impressions?

If your pages already get impressions but not enough clicks, we can help audit Search Console data, improve snippets, update content, and strengthen internal links.

Improve My CTR

25. Social Meta Title and Description Prompt

This prompt creates social titles and descriptions for Facebook, LinkedIn, X, and other share previews.

Best forSocial previews
OutputOpen Graph style text
Use whenYou need social snippets
Copy-paste prompt

How to use it: SEO title and social title do not need to be identical. Social snippets can be more conversational.

26. Image SEO Text Prompt

This prompt creates image alt text and image title text that supports the page topic without stuffing keywords.

Best forFeatured image SEO
OutputAlt text and image title
Use whenYou upload featured images
Copy-paste prompt

Why it works: Good alt text should describe the image first and support SEO naturally, not act like a keyword dump.

27. Meta Keywords and Tag Prompt

This prompt helps create focus keywords, secondary keywords, and WordPress tags that match the page instead of random broad terms.

Best forKeyword fields and tags
OutputFocus keyword and tags
Use whenYou finish SEO settings
Copy-paste prompt

How to use it: Use this after keyword research. Do not use tags as random keyword stuffing.

28. Duplicate Meta Description Check Prompt

This prompt checks whether multiple pages have snippets that are too similar and need to be rewritten.

Best forDuplicate snippet checks
OutputUniqueness recommendations
Use whenMany pages sound the same
Copy-paste prompt

Why it works: Large sites often end up with repetitive snippets. This prompt helps make each page distinct.

29. Final SEO Snippet Review Prompt

This prompt reviews the final snippet before publishing and checks for common problems.

Best forFinal review
OutputPublish-ready check
Use whenYou are ready to publish
Copy-paste prompt

How to use it: Use this as a final check before saving SEO plugin settings.

30. Complete Meta Optimization Workflow Prompt

This prompt creates a complete repeatable workflow for optimizing meta titles, descriptions, social snippets, image SEO, and keyword fields.

Best forFull metadata workflow
OutputStep-by-step SEO snippet system
Use whenYou want a repeatable process
Copy-paste prompt

Why it works: It turns metadata writing into a repeatable SEO workflow instead of a last-minute field you fill randomly.

Need help building a full SEO metadata workflow?

If you want better meta titles, meta descriptions, social previews, image SEO text, and WordPress SEO settings, we can help build a clean workflow for your site.

Contact Us

Meta Title and Description Best Practices

Meta titles and descriptions work best when they are written for both search intent and human decision-making. The title should clearly show the topic and keyword focus. The description should explain what the reader will get from the page and why the result is worth clicking.

Common Meta Title and Description Mistakes

The most common mistake is using a page title or first paragraph as the SEO snippet without editing it. This often creates titles that are too long and descriptions that do not explain the page value clearly enough.

AI Meta Title and Description FAQ

Can AI write good meta titles and meta descriptions?

Yes, AI can help create good meta titles and descriptions when the prompt includes the page topic, primary keyword, search intent, audience, page type, and real page summary. Human review is still important before publishing.

What is the best AI prompt for meta descriptions?

The best prompt asks AI to match the meta description to the search intent, keep it concise, use the main keyword naturally, explain the page value, and avoid unsupported claims or clickbait.

Should the SEO title be the same as the H1?

Not always. The H1 can be longer and more descriptive, while the SEO title should usually be shorter and optimized for how the page appears in search results.

How long should a meta description be?

A practical target is usually around 145-155 characters, but clarity matters more than exact character count. Search engines may also rewrite descriptions depending on the query.

Can a better meta description improve rankings?

A meta description is not usually a direct ranking factor, but a clearer and more relevant snippet can improve click-through rate, user expectations, and search result performance.

Should every page have a unique meta description?

Yes, important pages should have unique meta descriptions that match their specific topic, search intent, and value. Duplicate snippets make pages harder to distinguish in search results.

Conclusion

AI can make meta title and meta description writing faster, but the best results come from clear prompts and careful human review. A strong prompt tells AI the page type, primary keyword, search intent, audience, page promise, and tone. That helps create snippets that are clear, useful, accurate, and more likely to attract the right clicks.

Use these 30 AI prompts as a practical workflow for SEO snippets. Start with keyword and intent confirmation, create title and description options, check length, improve click value, write social snippets, add image SEO text, and review everything before publishing. For the full content workflow, connect this guide with ChatGPT prompts for SEO content writing and ChatGPT prompts for keyword research.

Image SEO

Featured image alt text: Futuristic SEO metadata dashboard with meta title editor, meta description preview, Google snippet card, CTR chart, keyword panel, and AI prompt library in neon UI style.

Featured image title: AI Prompts for Meta Titles and Meta Descriptions

SEO Metadata

SEO title: 30 AI Prompts for Meta Titles and Descriptions

Meta description: Use 30 AI prompts for meta titles, meta descriptions, SEO snippets, CTR improvement, WordPress SEO settings, and page optimization.

Focus keyword: AI prompts for meta titles and descriptions

Secondary keywords: AI meta description prompts, meta title prompts, SEO snippet prompts, ChatGPT meta description prompts, SEO title prompts, meta description generator prompts, CTR optimization prompts, WordPress SEO meta prompts, page optimization prompts

JavaScript localStorage is one of the most useful browser features for saving small pieces of data directly inside a visitor’s browser. It can be used for form drafts, shopping carts, dark mode preferences, saved filters, recently viewed products, favorite items, user settings, notes, quiz progress, dashboard widgets, and many other interactive website features that should remember something after the page reloads.

In this guide, you will find 30 JavaScript localStorage examples for real website projects, including saved form data, persistent shopping carts, dark mode settings, product favorites, recently viewed items, saved search filters, tab preferences, accordion states, draft notes, to-do lists, mini CRM notes, quiz progress, price calculator history, cookie-style banners, and reusable vanilla JavaScript storage components.

This post focuses on practical JavaScript localStorage logic, including localStorage.setItem(), localStorage.getItem(), localStorage.removeItem(), JSON storage, arrays, objects, form state, browser persistence, reset buttons, UI updates, and copy-paste-ready HTML, CSS, and JavaScript examples. For related JavaScript UI components, you can also explore our JavaScript form validation examples, JavaScript search filter examples, JavaScript dropdown menu examples, and JavaScript countdown timer examples.

View All 30 JavaScript LocalStorage Examples

What Is JavaScript LocalStorage?

JavaScript localStorage is a browser storage feature that lets websites save small pieces of data on the user’s device. Unlike temporary JavaScript variables, localStorage data can stay available after the page reloads, after the browser tab closes, and after the visitor returns later.

The main purpose of localStorage is to remember simple user data in the browser without needing a database for every small interaction. For example, a website can remember a visitor’s dark mode preference, a partially completed form, selected filters, favorite products, dismissed banners, cart items, or the last active tab in a content section.

LocalStorage is especially useful for front-end demos, UI components, small tools, ecommerce prototypes, landing pages, dashboards, and WordPress custom HTML blocks where you want a simple persistent browser feature. It is not a replacement for secure server-side storage, but it is very useful for interface state, preferences, and non-sensitive data.

Why LocalStorage Matters

LocalStorage matters because many modern websites need to remember small actions. A visitor may expect their form draft to stay available after refresh, their selected theme to stay active, their cart to stay visible, or their filters to remain selected while browsing a product list.

LocalStorage should still be used carefully. It is best for non-sensitive browser data. You should not store passwords, private tokens, payment details, personal identity numbers, or confidential data in localStorage. For simple UI memory, saved preferences, carts, drafts, and browser-based examples, it is one of the easiest JavaScript features to learn and use.

JavaScript LocalStorage Use Cases

There are many practical localStorage use cases for websites and web apps. Some are small interface improvements, while others can feel like complete mini applications. The key is to store the right amount of data and update the interface whenever the saved data changes.

A simple localStorage feature may save one value, such as a dark mode setting. A more advanced feature may save an array of cart products, an object with user preferences, a list of saved notes, multi-step form progress, or filter settings that rebuild the UI when the page loads again.

This guide focuses on JavaScript localStorage examples, so every demo will include visible JavaScript, HTML, and CSS code. The examples are designed to be copy-paste friendly, easy to customize, and different in layout, purpose, storage logic, UI behavior, and responsive design.

What Should a Good LocalStorage Feature Include?

A good localStorage feature should be useful, predictable, and easy to reset. Users should understand what is being saved, the interface should update immediately after changes, and the saved data should load correctly when the page opens again.

Clear saved data purpose

The feature should have a clear reason to save data, such as a form draft, cart, preference, favorite item, note, or progress state.

Reliable page reload behavior

The saved value should appear again after refresh, and the UI should match what is stored in the browser.

Safe data handling

LocalStorage should be used for non-sensitive data only. Private, secure, or payment-related information belongs on the server.

Reset and remove option

Users should be able to clear saved drafts, remove cart items, reset preferences, or delete stored browser data when needed.

Before building a localStorage feature, decide exactly what should be saved and what should happen when the visitor returns. Should the form refill automatically? Should the cart rebuild itself? Should the selected tab reopen? Should the user see a “clear saved data” button? These small decisions make the difference between a basic demo and a professional JavaScript component.

You can combine JavaScript localStorage with many other website UI patterns. Saved form drafts work well with modern CSS forms, persistent carts can support ecommerce layouts and modern CSS pricing tables, saved tabs can be used with modern CSS tabs, and preference panels can fit inside layouts from our modern CSS layouts guide.

30 JavaScript LocalStorage Examples

Now let’s look at 30 JavaScript localStorage examples for real website projects. Each example uses a different storage purpose, layout style, interface pattern, saved data structure, browser persistence behavior, reset option, form logic, cart logic, preference setting, or responsive UI approach, so you can build useful browser-based features with visible JavaScript, HTML, and CSS code.

1. Auto-Save Contact Form Draft with LocalStorage

An auto-save contact form draft is one of the most practical JavaScript localStorage examples. It helps users keep their typed message, name, email, and project details even if they accidentally refresh the page or leave the browser tab.

This example saves form fields automatically while the visitor types. When the page loads again, JavaScript reads the saved values from localStorage and restores the form. The user can also clear the saved draft with one button.

Example 01

Saved Contact Draft

This form saves your draft in the browser while you type. Refresh the page and the values will still be there.

Draft status: waiting for input
No saved draft yet

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-one]");
  if (!wrapper) return;

  const storageKey = "vbStorageOneContactDraft";
  const form = wrapper.querySelector(".vb-storage-one-form");
  const status = wrapper.querySelector("[data-status]");
  const savedTime = wrapper.querySelector("[data-saved-time]");
  const clearButton = wrapper.querySelector("[data-clear]");

  const fields = {
    name: form.elements.name,
    email: form.elements.email,
    project: form.elements.project,
    message: form.elements.message
  };

  function getDraftData() {
    return {
      name: fields.name.value,
      email: fields.email.value,
      project: fields.project.value,
      message: fields.message.value,
      savedAt: new Date().toLocaleTimeString([], {
        hour: "2-digit",
        minute: "2-digit"
      })
    };
  }

  function saveDraft() {
    const draft = getDraftData();
    localStorage.setItem(storageKey, JSON.stringify(draft));
    status.textContent = "Draft status: saved in this browser";
    savedTime.textContent = "Last saved at " + draft.savedAt;
  }

  function loadDraft() {
    const savedDraft = localStorage.getItem(storageKey);
    if (!savedDraft) return;

    try {
      const draft = JSON.parse(savedDraft);

      fields.name.value = draft.name || "";
      fields.email.value = draft.email || "";
      fields.project.value = draft.project || "";
      fields.message.value = draft.message || "";

      status.textContent = "Draft status: restored from localStorage";
      savedTime.textContent = draft.savedAt
        ? "Restored draft saved at " + draft.savedAt
        : "Restored saved draft";
    } catch (error) {
      localStorage.removeItem(storageKey);
      status.textContent = "Draft status: saved data was reset";
    }
  }

  function clearDraft() {
    localStorage.removeItem(storageKey);
    form.reset();
    status.textContent = "Draft status: cleared";
    savedTime.textContent = "No saved draft yet";
  }

  Object.values(fields).forEach(function (field) {
    field.addEventListener("input", saveDraft);
    field.addEventListener("change", saveDraft);
  });

  clearButton.addEventListener("click", clearDraft);

  loadDraft();
})();

HTML

<div class="vb-storage-one-demo">
  <div class="vb-storage-one-card" data-vb-storage-one>
    <div class="vb-storage-one-info">
      <span class="vb-storage-one-kicker">Example 01</span>
      <h3>Saved Contact Draft</h3>
      <p>This form saves your draft in the browser while you type. Refresh the page and the values will still be there.</p>

      <div class="vb-storage-one-status" data-status>
        Draft status: waiting for input
      </div>
    </div>

    <form class="vb-storage-one-form">
      <label>
        <span>Your name</span>
        <input type="text" name="name" placeholder="Alex Morgan">
      </label>

      <label>
        <span>Email address</span>
        <input type="email" name="email" placeholder="alex@example.com">
      </label>

      <label>
        <span>Project type</span>
        <select name="project">
          <option value="">Choose project type</option>
          <option value="Website design">Website design</option>
          <option value="WordPress plugin">WordPress plugin</option>
          <option value="Ecommerce store">Ecommerce store</option>
          <option value="JavaScript feature">JavaScript feature</option>
        </select>
      </label>

      <label>
        <span>Project message</span>
        <textarea name="message" rows="5" placeholder="Tell us what you want to build..."></textarea>
      </label>

      <div class="vb-storage-one-actions">
        <button type="button" data-clear>Clear Saved Draft</button>
        <span data-saved-time>No saved draft yet</span>
      </div>
    </form>
  </div>
</div>

CSS

.vb-storage-one-demo,
.vb-storage-one-demo * {
  box-sizing: border-box;
}

.vb-storage-one-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 16% 12%, rgba(59, 130, 246, 0.18), transparent 34%),
    radial-gradient(circle at 86% 20%, rgba(16, 185, 129, 0.18), transparent 36%),
    linear-gradient(135deg, #eff6ff 0%, #f0fdfa 55%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-storage-one-card {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(22px, 4vw, 34px);
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.12);
}

.vb-storage-one-info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
}

.vb-storage-one-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-one-info h3 {
  margin: 0 0 16px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 68px) !important;
  line-height: 0.94 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-one-info p {
  max-width: 520px;
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-storage-one-status {
  display: inline-flex;
  align-self: flex-start;
  padding: 13px 15px;
  border-radius: 18px;
  background: #ecfdf5;
  border: 1px solid rgba(16, 185, 129, 0.24);
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 850;
}

.vb-storage-one-form {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 24px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-one-form label {
  display: grid;
  gap: 7px;
}

.vb-storage-one-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-one-form input,
.vb-storage-one-form select,
.vb-storage-one-form textarea {
  width: 100%;
  border: 1px solid rgba(148, 163, 184, 0.34);
  border-radius: 16px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 650;
  outline: none;
  box-shadow: none;
}

.vb-storage-one-form input,
.vb-storage-one-form select {
  min-height: 48px;
  padding: 0 14px;
}

.vb-storage-one-form textarea {
  resize: vertical;
  padding: 13px 14px;
  line-height: 1.55;
}

.vb-storage-one-form input:focus,
.vb-storage-one-form select:focus,
.vb-storage-one-form textarea:focus {
  border-color: rgba(37, 99, 235, 0.76);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.10);
}

.vb-storage-one-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 2px;
}

.vb-storage-one-actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 11px 15px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #06b6d4);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(37, 99, 235, 0.22);
}

.vb-storage-one-actions span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  font-weight: 750;
}

@media (max-width: 880px) {
  .vb-storage-one-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-one-card {
    padding: 18px;
    border-radius: 22px;
  }

  .vb-storage-one-info h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-one-actions button {
    width: 100%;
  }
}

This localStorage form draft example is useful for contact pages, lead generation forms, quote request forms, support forms, onboarding forms, and long message fields where users should not lose their progress after a page refresh.

2. LocalStorage Shopping Cart with Quantity Updates

A localStorage shopping cart is a great way to learn how browser storage works with arrays, objects, buttons, totals, and dynamic UI rendering. It can store cart items in the browser without a backend, making it useful for demos, prototypes, and small front-end ecommerce examples.

This example lets users add products to a mini cart, increase or decrease quantities, remove items, clear the cart, and keep the cart saved after refresh. JavaScript stores the cart as a JSON array in localStorage and rebuilds the cart interface on page load.

Example 02

Persistent Mini Cart

Add products to the cart, refresh the page, and the selected items will still be saved in localStorage.

JS

JavaScript UI Kit

Reusable UI components

29€
WP

WordPress Snippet Pack

Useful frontend features

39€
SEO

SEO Schema Template

Structured data starter

19€
Your Cart 0 items

Your cart is empty.

Total 0€

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-two]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwoMiniCart";
  const addButtons = wrapper.querySelectorAll("[data-add-cart]");
  const cartList = wrapper.querySelector("[data-cart-list]");
  const cartCount = wrapper.querySelector("[data-cart-count]");
  const cartTotal = wrapper.querySelector("[data-cart-total]");
  const clearCartButton = wrapper.querySelector("[data-clear-cart]");

  let cart = [];

  function saveCart() {
    localStorage.setItem(storageKey, JSON.stringify(cart));
  }

  function loadCart() {
    const savedCart = localStorage.getItem(storageKey);
    if (!savedCart) return;

    try {
      const parsedCart = JSON.parse(savedCart);
      cart = Array.isArray(parsedCart) ? parsedCart : [];
    } catch (error) {
      cart = [];
      localStorage.removeItem(storageKey);
    }
  }

  function getTotalItems() {
    return cart.reduce(function (sum, item) {
      return sum + item.quantity;
    }, 0);
  }

  function getTotalPrice() {
    return cart.reduce(function (sum, item) {
      return sum + item.price * item.quantity;
    }, 0);
  }

  function renderCart() {
    cartList.innerHTML = "";

    if (cart.length === 0) {
      cartList.innerHTML = '<p class="vb-storage-two-empty">Your cart is empty.</p>';
    } else {
      cart.forEach(function (item) {
        const cartItem = document.createElement("div");
        cartItem.className = "vb-storage-two-cart-item";

        cartItem.innerHTML =
          '<div>' +
            '<h5>' + item.name + '</h5>' +
            '<p>' + item.price + '€ each</p>' +
          '</div>' +
          '<div class="vb-storage-two-qty">' +
            '<button type="button" data-decrease="' + item.id + '">-</button>' +
            '<span>' + item.quantity + '</span>' +
            '<button type="button" data-increase="' + item.id + '">+</button>' +
          '</div>';

        cartList.appendChild(cartItem);
      });
    }

    const totalItems = getTotalItems();
    cartCount.textContent = totalItems === 1 ? "1 item" : totalItems + " items";
    cartTotal.textContent = getTotalPrice() + "€";
  }

  function addToCart(product) {
    const existingItem = cart.find(function (item) {
      return item.id === product.id;
    });

    if (existingItem) {
      existingItem.quantity += 1;
    } else {
      cart.push({
        id: product.id,
        name: product.name,
        price: product.price,
        quantity: 1
      });
    }

    saveCart();
    renderCart();
  }

  function changeQuantity(productId, direction) {
    const item = cart.find(function (cartItem) {
      return cartItem.id === productId;
    });

    if (!item) return;

    item.quantity += direction;

    if (item.quantity <= 0) {
      cart = cart.filter(function (cartItem) {
        return cartItem.id !== productId;
      });
    }

    saveCart();
    renderCart();
  }

  addButtons.forEach(function (button) {
    button.addEventListener("click", function () {
      addToCart({
        id: button.getAttribute("data-id"),
        name: button.getAttribute("data-name"),
        price: Number(button.getAttribute("data-price"))
      });
    });
  });

  cartList.addEventListener("click", function (event) {
    const increaseId = event.target.getAttribute("data-increase");
    const decreaseId = event.target.getAttribute("data-decrease");

    if (increaseId) {
      changeQuantity(increaseId, 1);
    }

    if (decreaseId) {
      changeQuantity(decreaseId, -1);
    }
  });

  clearCartButton.addEventListener("click", function () {
    cart = [];
    saveCart();
    renderCart();
  });

  loadCart();
  renderCart();
})();

HTML

<div class="vb-storage-two-demo">
  <div class="vb-storage-two-shop" data-vb-storage-two>
    <div class="vb-storage-two-header">
      <span class="vb-storage-two-kicker">Example 02</span>
      <h3>Persistent Mini Cart</h3>
      <p>Add products to the cart, refresh the page, and the selected items will still be saved in localStorage.</p>
    </div>

    <div class="vb-storage-two-layout">
      <div class="vb-storage-two-products">
        <article class="vb-storage-two-product">
          <div class="vb-storage-two-product-icon">JS</div>
          <div>
            <h4>JavaScript UI Kit</h4>
            <p>Reusable UI components</p>
          </div>
          <strong>29€</strong>
          <button type="button" data-add-cart data-id="ui-kit" data-name="JavaScript UI Kit" data-price="29">Add</button>
        </article>

        <article class="vb-storage-two-product">
          <div class="vb-storage-two-product-icon">WP</div>
          <div>
            <h4>WordPress Snippet Pack</h4>
            <p>Useful frontend features</p>
          </div>
          <strong>39€</strong>
          <button type="button" data-add-cart data-id="wp-pack" data-name="WordPress Snippet Pack" data-price="39">Add</button>
        </article>

        <article class="vb-storage-two-product">
          <div class="vb-storage-two-product-icon">SEO</div>
          <div>
            <h4>SEO Schema Template</h4>
            <p>Structured data starter</p>
          </div>
          <strong>19€</strong>
          <button type="button" data-add-cart data-id="schema-template" data-name="SEO Schema Template" data-price="19">Add</button>
        </article>
      </div>

      <div class="vb-storage-two-cart">
        <div class="vb-storage-two-cart-head">
          <strong>Your Cart</strong>
          <span data-cart-count>0 items</span>
        </div>

        <div class="vb-storage-two-cart-list" data-cart-list>
          <p class="vb-storage-two-empty">Your cart is empty.</p>
        </div>

        <div class="vb-storage-two-total">
          <span>Total</span>
          <strong data-cart-total>0€</strong>
        </div>

        <button class="vb-storage-two-clear" type="button" data-clear-cart>Clear Cart</button>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-two-demo,
.vb-storage-two-demo * {
  box-sizing: border-box;
}

.vb-storage-two-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 42px;
  background:
    radial-gradient(circle at 12% 18%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 88% 14%, rgba(168, 85, 247, 0.18), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #eef2ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.18);
  box-shadow: 0 28px 80px rgba(79, 70, 229, 0.10);
}

.vb-storage-two-shop {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #0f172a;
  overflow: hidden;
  box-shadow: 0 28px 90px rgba(15, 23, 42, 0.28);
}

.vb-storage-two-header {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-two-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-two-header h3 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-two-header p {
  max-width: 720px;
  margin: 0 !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-two-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(310px, 390px);
  gap: 20px;
  align-items: start;
}

.vb-storage-two-products {
  display: grid;
  gap: 14px;
}

.vb-storage-two-product {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) auto auto;
  gap: 14px;
  align-items: center;
  min-width: 0;
  padding: 16px;
  border-radius: 24px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-two-product-icon {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: 20px;
  background: linear-gradient(135deg, #2563eb, #7c3aed);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 17px;
  font-weight: 950;
}

.vb-storage-two-product h4 {
  margin: 0 0 5px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 17px !important;
  line-height: 1.2 !important;
  font-weight: 900 !important;
}

.vb-storage-two-product p {
  margin: 0 !important;
  color: #94a3b8 !important;
  -webkit-text-fill-color: #94a3b8 !important;
  font-size: 13px;
  font-weight: 650;
}

.vb-storage-two-product strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 19px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-storage-two-product button,
.vb-storage-two-clear {
  border: 0;
  cursor: pointer;
  font-weight: 950;
}

.vb-storage-two-product button {
  min-height: 42px;
  padding: 10px 14px;
  border-radius: 999px;
  background: #ffffff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 13px;
}

.vb-storage-two-cart {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 26px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
  box-shadow: 0 20px 60px rgba(2, 6, 23, 0.20);
}

.vb-storage-two-cart-head,
.vb-storage-two-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-two-cart-head strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px;
  font-weight: 950;
}

.vb-storage-two-cart-head span {
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-two-cart-list {
  display: grid;
  gap: 10px;
  min-height: 110px;
}

.vb-storage-two-empty {
  display: grid;
  place-items: center;
  min-height: 110px;
  margin: 0 !important;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  border-radius: 18px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-two-cart-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  padding: 12px;
  border-radius: 18px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-two-cart-item h5 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px !important;
  line-height: 1.2 !important;
  font-weight: 900 !important;
}

.vb-storage-two-cart-item p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 750;
}

.vb-storage-two-qty {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.vb-storage-two-qty button {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 999px;
  background: #e0e7ff;
  color: #3730a3 !important;
  -webkit-text-fill-color: #3730a3 !important;
  font-size: 16px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-two-qty span {
  min-width: 24px;
  text-align: center;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-two-total {
  padding: 14px;
  border-radius: 18px;
  background: #eff6ff;
}

.vb-storage-two-total span {
  color: #1e3a8a !important;
  -webkit-text-fill-color: #1e3a8a !important;
  font-size: 13px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-storage-two-total strong {
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 26px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-two-clear {
  min-height: 44px;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #7c3aed);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
}

@media (max-width: 980px) {
  .vb-storage-two-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 680px) {
  .vb-storage-two-product {
    grid-template-columns: 54px minmax(0, 1fr);
  }

  .vb-storage-two-product strong,
  .vb-storage-two-product button {
    grid-column: 2;
    justify-self: start;
  }

  .vb-storage-two-product-icon {
    width: 54px;
    height: 54px;
    border-radius: 17px;
  }
}

This localStorage shopping cart example is useful for ecommerce prototypes, product landing pages, mini carts, digital product pages, simple checkout demos, and JavaScript projects that need persistent cart state without a backend.

3. Dark Mode Preference Saved with LocalStorage

A dark mode preference saved with localStorage lets visitors choose their preferred interface style and keep that choice after refreshing or returning to the page later. This is one of the most common and useful localStorage patterns for modern websites.

This example uses a polished settings card with light mode, dark mode, and system-style buttons. JavaScript saves the selected theme name in localStorage, updates the demo instantly, and restores the same theme when the page loads again.

Example 03

Saved Theme Preference

Choose a theme and refresh the page. The selected style is saved in localStorage and restored automatically.

Current theme: Soft
UI

Dashboard Appearance

Your theme preference is stored locally in the browser, so this dashboard remembers the selected look.

Saved setting theme: soft
Storage type localStorage

JavaScript

(function () {
  const app = document.querySelector("[data-vb-storage-three]");
  if (!app) return;

  const storageKey = "vbStorageThreeTheme";
  const buttons = app.querySelectorAll("[data-theme-button]");
  const currentTheme = app.querySelector("[data-current-theme]");
  const settingLabel = app.querySelector("[data-setting-label]");

  function formatThemeName(theme) {
    return theme.charAt(0).toUpperCase() + theme.slice(1);
  }

  function applyTheme(theme) {
    app.setAttribute("data-theme", theme);
    currentTheme.textContent = formatThemeName(theme);
    settingLabel.textContent = "theme: " + theme;

    buttons.forEach(function (button) {
      const isActive = button.getAttribute("data-theme-button") === theme;
      button.classList.toggle("is-active", isActive);
    });
  }

  function saveTheme(theme) {
    localStorage.setItem(storageKey, theme);
    applyTheme(theme);
  }

  buttons.forEach(function (button) {
    button.addEventListener("click", function () {
      const selectedTheme = button.getAttribute("data-theme-button");
      saveTheme(selectedTheme);
    });
  });

  const savedTheme = localStorage.getItem(storageKey) || "soft";
  applyTheme(savedTheme);
})();

HTML

<div class="vb-storage-three-demo">
  <div class="vb-storage-three-app" data-vb-storage-three data-theme="soft">
    <div class="vb-storage-three-sidebar">
      <span class="vb-storage-three-kicker">Example 03</span>
      <h3>Saved Theme Preference</h3>
      <p>Choose a theme and refresh the page. The selected style is saved in localStorage and restored automatically.</p>

      <div class="vb-storage-three-buttons" role="group" aria-label="Theme preference buttons">
        <button type="button" data-theme-button="light">Light</button>
        <button type="button" data-theme-button="dark">Dark</button>
        <button type="button" data-theme-button="soft">Soft</button>
      </div>

      <div class="vb-storage-three-current">
        Current theme: <strong data-current-theme>Soft</strong>
      </div>
    </div>

    <div class="vb-storage-three-panel">
      <div class="vb-storage-three-topbar">
        <span></span>
        <span></span>
        <span></span>
      </div>

      <div class="vb-storage-three-content">
        <div class="vb-storage-three-avatar">UI</div>
        <div>
          <h4>Dashboard Appearance</h4>
          <p>Your theme preference is stored locally in the browser, so this dashboard remembers the selected look.</p>
        </div>
      </div>

      <div class="vb-storage-three-metrics">
        <div>
          <span>Saved setting</span>
          <strong data-setting-label>theme: soft</strong>
        </div>

        <div>
          <span>Storage type</span>
          <strong>localStorage</strong>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-three-demo,
.vb-storage-three-demo * {
  box-sizing: border-box;
}

.vb-storage-three-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 22px 54px 22px 54px;
  background:
    linear-gradient(90deg, rgba(15, 23, 42, 0.045) 1px, transparent 1px),
    linear-gradient(0deg, rgba(15, 23, 42, 0.045) 1px, transparent 1px),
    linear-gradient(135deg, #ffffff 0%, #f8fafc 100%) !important;
  background-size: 26px 26px, 26px 26px, auto !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-storage-three-app {
  display: grid;
  grid-template-columns: minmax(0, 0.78fr) minmax(0, 1.22fr);
  gap: 22px;
  max-width: 1140px;
  margin: 0 auto;
  padding: clamp(18px, 4vw, 30px);
  border-radius: 18px 42px 18px 42px;
  transition: background 0.25s ease, color 0.25s ease;
}

.vb-storage-three-app[data-theme="light"] {
  background: #ffffff;
  color: #0f172a;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.12);
}

.vb-storage-three-app[data-theme="dark"] {
  background: #0f172a;
  color: #ffffff;
  box-shadow: 0 24px 70px rgba(2, 6, 23, 0.34);
}

.vb-storage-three-app[data-theme="soft"] {
  background: linear-gradient(135deg, #eef2ff, #ecfdf5);
  color: #0f172a;
  box-shadow: 0 24px 70px rgba(79, 70, 229, 0.14);
}

.vb-storage-three-sidebar,
.vb-storage-three-panel {
  min-width: 0;
}

.vb-storage-three-sidebar {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 28px);
  border-radius: 18px 34px 18px 34px;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-sidebar {
  background: #f8fafc;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-sidebar {
  background: rgba(255,255,255,0.07);
}

.vb-storage-three-app[data-theme="soft"] .vb-storage-three-sidebar {
  background: rgba(255,255,255,0.72);
}

.vb-storage-three-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-kicker {
  background: rgba(96, 165, 250, 0.18);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
}

.vb-storage-three-sidebar h3 {
  margin: 0 0 16px !important;
  font-size: clamp(35px, 5.8vw, 66px) !important;
  line-height: 0.95 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-sidebar h3,
.vb-storage-three-app[data-theme="soft"] .vb-storage-three-sidebar h3 {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-sidebar h3 {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-three-sidebar p {
  max-width: 520px;
  margin: 0 0 22px !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-sidebar p,
.vb-storage-three-app[data-theme="soft"] .vb-storage-three-sidebar p {
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-sidebar p {
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
}

.vb-storage-three-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
}

.vb-storage-three-buttons button {
  min-height: 44px;
  padding: 10px 15px;
  border: 0;
  border-radius: 999px;
  background: #2563eb;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 34px rgba(37, 99, 235, 0.20);
}

.vb-storage-three-buttons button.is-active {
  background: linear-gradient(135deg, #10b981, #06b6d4);
}

.vb-storage-three-current {
  display: inline-flex;
  align-self: flex-start;
  padding: 12px 14px;
  border-radius: 16px;
  font-size: 14px;
  font-weight: 850;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-current,
.vb-storage-three-app[data-theme="soft"] .vb-storage-three-current {
  background: #ffffff;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-current {
  background: rgba(255,255,255,0.09);
  color: #e2e8f0 !important;
  -webkit-text-fill-color: #e2e8f0 !important;
}

.vb-storage-three-current strong {
  margin-left: 5px;
}

.vb-storage-three-panel {
  display: grid;
  gap: 18px;
  align-content: center;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 34px 12px 34px 12px;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-panel {
  background: #eff6ff;
  border: 1px solid rgba(37, 99, 235, 0.16);
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-panel {
  background: rgba(255,255,255,0.07);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-three-app[data-theme="soft"] .vb-storage-three-panel {
  background: rgba(255,255,255,0.76);
  border: 1px solid rgba(255,255,255,0.72);
}

.vb-storage-three-topbar {
  display: flex;
  gap: 8px;
}

.vb-storage-three-topbar span {
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: #60a5fa;
}

.vb-storage-three-topbar span:nth-child(2) {
  background: #34d399;
}

.vb-storage-three-topbar span:nth-child(3) {
  background: #a78bfa;
}

.vb-storage-three-content {
  display: grid;
  grid-template-columns: 78px minmax(0, 1fr);
  gap: 18px;
  align-items: center;
  padding: 22px;
  border-radius: 26px;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-content,
.vb-storage-three-app[data-theme="soft"] .vb-storage-three-content {
  background: #ffffff;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-content {
  background: rgba(15, 23, 42, 0.70);
}

.vb-storage-three-avatar {
  display: grid;
  place-items: center;
  width: 78px;
  height: 78px;
  border-radius: 24px;
  background: linear-gradient(135deg, #2563eb, #10b981);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  font-weight: 950;
}

.vb-storage-three-content h4 {
  margin: 0 0 8px !important;
  font-size: clamp(22px, 3vw, 32px) !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-content h4,
.vb-storage-three-app[data-theme="soft"] .vb-storage-three-content h4 {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-content h4 {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-three-content p {
  margin: 0 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-content p,
.vb-storage-three-app[data-theme="soft"] .vb-storage-three-content p {
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-content p {
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
}

.vb-storage-three-metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-storage-three-metrics div {
  padding: 16px;
  border-radius: 22px;
}

.vb-storage-three-app[data-theme="light"] .vb-storage-three-metrics div,
.vb-storage-three-app[data-theme="soft"] .vb-storage-three-metrics div {
  background: #ffffff;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-metrics div {
  background: rgba(15, 23, 42, 0.70);
}

.vb-storage-three-metrics span {
  display: block;
  margin-bottom: 7px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-storage-three-metrics strong {
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
  font-size: 18px;
  line-height: 1.2;
  font-weight: 950;
}

.vb-storage-three-app[data-theme="dark"] .vb-storage-three-metrics strong {
  color: #93c5fd !important;
  -webkit-text-fill-color: #93c5fd !important;
}

@media (max-width: 900px) {
  .vb-storage-three-app {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-three-content {
    grid-template-columns: 1fr;
  }

  .vb-storage-three-metrics {
    grid-template-columns: 1fr;
  }

  .vb-storage-three-sidebar h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This dark mode localStorage example is useful for dashboards, blogs, SaaS apps, user account pages, admin panels, documentation websites, and any interface where users should be able to keep their preferred visual theme.

4. Recently Viewed Products Saved with LocalStorage

A recently viewed products section is a very practical localStorage feature for ecommerce websites, product catalogs, affiliate sites, digital product shops, and WooCommerce-style layouts. It helps visitors return to products they checked earlier without needing a backend system.

This example lets users click product cards and stores the latest viewed products in localStorage. The recently viewed list updates instantly, avoids duplicates, keeps the newest product first, and restores the same list after page refresh.

Example 04

Recently Viewed Products

Click a product card. The recently viewed list is saved in localStorage and restored after refresh.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-four]");
  if (!wrapper) return;

  const storageKey = "vbStorageFourRecentlyViewed";
  const productButtons = wrapper.querySelectorAll("[data-view-product]");
  const recentList = wrapper.querySelector("[data-recent-list]");
  const clearButton = wrapper.querySelector("[data-clear-recent]");

  let recentlyViewed = [];

  function saveRecentlyViewed() {
    localStorage.setItem(storageKey, JSON.stringify(recentlyViewed));
  }

  function loadRecentlyViewed() {
    const savedItems = localStorage.getItem(storageKey);
    if (!savedItems) return;

    try {
      const parsedItems = JSON.parse(savedItems);
      recentlyViewed = Array.isArray(parsedItems) ? parsedItems : [];
    } catch (error) {
      recentlyViewed = [];
      localStorage.removeItem(storageKey);
    }
  }

  function renderRecentlyViewed() {
    recentList.innerHTML = "";

    if (recentlyViewed.length === 0) {
      recentList.innerHTML = '<p class="vb-storage-four-empty">No products viewed yet.</p>';
      return;
    }

    recentlyViewed.forEach(function (product) {
      const item = document.createElement("div");
      item.className = "vb-storage-four-recent-item";

      item.innerHTML =
        '<div class="vb-storage-four-recent-icon">' + product.shortLabel + '</div>' +
        '<div>' +
          '<strong>' + product.name + '</strong>' +
          '<span>' + product.category + ' · ' + product.price + '</span>' +
        '</div>';

      recentList.appendChild(item);
    });
  }

  function addViewedProduct(product) {
    recentlyViewed = recentlyViewed.filter(function (item) {
      return item.id !== product.id;
    });

    recentlyViewed.unshift(product);
    recentlyViewed = recentlyViewed.slice(0, 4);

    saveRecentlyViewed();
    renderRecentlyViewed();
  }

  productButtons.forEach(function (button) {
    button.addEventListener("click", function () {
      const product = {
        id: button.getAttribute("data-id"),
        name: button.getAttribute("data-name"),
        category: button.getAttribute("data-category"),
        price: button.getAttribute("data-price"),
        shortLabel: button.querySelector(".vb-storage-four-visual").textContent
      };

      addViewedProduct(product);
    });
  });

  clearButton.addEventListener("click", function () {
    recentlyViewed = [];
    saveRecentlyViewed();
    renderRecentlyViewed();
  });

  loadRecentlyViewed();
  renderRecentlyViewed();
})();

HTML

<div class="vb-storage-four-demo">
  <div class="vb-storage-four-shop" data-vb-storage-four>
    <div class="vb-storage-four-intro">
      <span class="vb-storage-four-kicker">Example 04</span>
      <h3>Recently Viewed Products</h3>
      <p>Click a product card. The recently viewed list is saved in localStorage and restored after refresh.</p>
    </div>

    <div class="vb-storage-four-grid">
      <div class="vb-storage-four-products">
        <button type="button" class="vb-storage-four-product" data-view-product data-id="greenhouse-kit" data-name="Greenhouse Starter Kit" data-category="Garden" data-price="249€">
          <span class="vb-storage-four-visual">GH</span>
          <strong>Greenhouse Starter Kit</strong>
          <small>Garden · 249€</small>
        </button>

        <button type="button" class="vb-storage-four-product" data-view-product data-id="seo-plugin" data-name="SEO Schema Plugin" data-category="WordPress" data-price="29€">
          <span class="vb-storage-four-visual">SEO</span>
          <strong>SEO Schema Plugin</strong>
          <small>WordPress · 29€</small>
        </button>

        <button type="button" class="vb-storage-four-product" data-view-product data-id="ui-template" data-name="Landing Page UI Template" data-category="Design" data-price="19€">
          <span class="vb-storage-four-visual">UI</span>
          <strong>Landing Page UI Template</strong>
          <small>Design · 19€</small>
        </button>

        <button type="button" class="vb-storage-four-product" data-view-product data-id="cart-script" data-name="Mini Cart JavaScript" data-category="JavaScript" data-price="15€">
          <span class="vb-storage-four-visual">JS</span>
          <strong>Mini Cart JavaScript</strong>
          <small>JavaScript · 15€</small>
        </button>
      </div>

      <aside class="vb-storage-four-sidebar">
        <div class="vb-storage-four-sidebar-head">
          <strong>Recently Viewed</strong>
          <button type="button" data-clear-recent>Clear</button>
        </div>

        <div class="vb-storage-four-list" data-recent-list>
          <p class="vb-storage-four-empty">No products viewed yet.</p>
        </div>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-storage-four-demo,
.vb-storage-four-demo * {
  box-sizing: border-box;
}

.vb-storage-four-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 42px;
  background:
    radial-gradient(circle at 12% 18%, rgba(34, 197, 94, 0.18), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(14, 165, 233, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #f8fafc 58%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.18);
  box-shadow: 0 28px 80px rgba(22, 101, 52, 0.10);
}

.vb-storage-four-shop {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-four-intro {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-four-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-four-intro h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-four-intro p {
  max-width: 740px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-four-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
  gap: 20px;
  align-items: start;
}

.vb-storage-four-products {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.vb-storage-four-product {
  display: grid;
  gap: 12px;
  min-width: 0;
  padding: 18px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  border-radius: 26px;
  background: #f8fafc;
  text-align: left;
  cursor: pointer;
  transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
}

.vb-storage-four-product:hover {
  transform: translateY(-3px);
  border-color: rgba(34, 197, 94, 0.50);
  box-shadow: 0 18px 44px rgba(15, 23, 42, 0.10);
}

.vb-storage-four-visual {
  display: grid;
  place-items: center;
  width: 68px;
  height: 68px;
  border-radius: 22px;
  background: linear-gradient(135deg, #22c55e, #0ea5e9);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
}

.vb-storage-four-product strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px;
  line-height: 1.2;
  font-weight: 950;
}

.vb-storage-four-product small {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  font-weight: 750;
}

.vb-storage-four-sidebar {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 28px;
  background: #0f172a;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.24);
}

.vb-storage-four-sidebar-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-four-sidebar-head strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  font-weight: 950;
}

.vb-storage-four-sidebar-head button {
  min-height: 36px;
  padding: 8px 12px;
  border: 0;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
  font-weight: 900;
  cursor: pointer;
}

.vb-storage-four-list {
  display: grid;
  gap: 10px;
  min-height: 220px;
}

.vb-storage-four-empty {
  display: grid;
  place-items: center;
  min-height: 220px;
  margin: 0 !important;
  border: 1px dashed rgba(255,255,255,0.22);
  border-radius: 20px;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-four-recent-item {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 12px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-four-recent-icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 16px;
  background: linear-gradient(135deg, #22c55e, #0ea5e9);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-four-recent-item strong {
  display: block;
  margin-bottom: 5px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.2;
  font-weight: 900;
}

.vb-storage-four-recent-item span {
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 12px;
  font-weight: 800;
}

@media (max-width: 940px) {
  .vb-storage-four-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-four-products {
    grid-template-columns: 1fr;
  }

  .vb-storage-four-intro h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This recently viewed products localStorage example is useful for ecommerce stores, WooCommerce product pages, affiliate product roundups, comparison websites, product directories, and catalog-style landing pages.

5. Saved Search Filters with LocalStorage

Saved search filters are useful for product directories, blog archives, job boards, real estate listings, service pages, SaaS dashboards, course libraries, and any interface where users filter a list. LocalStorage can remember the selected search text, category, price range, or sorting option after refresh.

This example uses a small resource directory with search, category filter, and sort options. JavaScript saves the selected filter state in localStorage, restores it when the page loads again, and updates the visible results automatically.

Example 05

Saved Search Filters

Search, filter, sort, refresh the page, and your selected directory filters will stay saved in localStorage.

0 results Filters are ready.
JavaScript

JavaScript LocalStorage Guide

Save form data, carts, preferences, and UI state in the browser.

WordPress

WordPress Custom Plugin Starter

Build lightweight WordPress plugins with custom frontend features.

SEO

Entity SEO Checklist

Improve structured data, internal links, and AI-readable content.

JavaScript

JavaScript Filter UI Examples

Create searchable and filterable user interfaces with vanilla JavaScript.

WordPress

WooCommerce Product UX Tips

Improve product cards, checkout flows, quote forms, and mini carts.

SEO

Schema Markup for Blogs

Add useful structured data to long-form SEO content and guides.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-five]");
  if (!wrapper) return;

  const storageKey = "vbStorageFiveFilters";
  const searchInput = wrapper.querySelector("[data-search]");
  const categorySelect = wrapper.querySelector("[data-category]");
  const sortSelect = wrapper.querySelector("[data-sort]");
  const resetButton = wrapper.querySelector("[data-reset-filters]");
  const resultCount = wrapper.querySelector("[data-result-count]");
  const filterStatus = wrapper.querySelector("[data-filter-status]");
  const resultsContainer = wrapper.querySelector("[data-results]");
  const items = Array.from(wrapper.querySelectorAll("[data-item]"));

  function getFilterState() {
    return {
      search: searchInput.value.trim().toLowerCase(),
      category: categorySelect.value,
      sort: sortSelect.value
    };
  }

  function saveFilterState() {
    localStorage.setItem(storageKey, JSON.stringify(getFilterState()));
  }

  function loadFilterState() {
    const savedState = localStorage.getItem(storageKey);
    if (!savedState) return;

    try {
      const state = JSON.parse(savedState);
      searchInput.value = state.search || "";
      categorySelect.value = state.category || "all";
      sortSelect.value = state.sort || "popular";
    } catch (error) {
      localStorage.removeItem(storageKey);
    }
  }

  function sortItems(itemsToSort, sortType) {
    return itemsToSort.slice().sort(function (a, b) {
      if (sortType === "az") {
        return a.dataset.title.localeCompare(b.dataset.title);
      }

      if (sortType === "newest") {
        return new Date(b.dataset.date) - new Date(a.dataset.date);
      }

      return Number(b.dataset.popularity) - Number(a.dataset.popularity);
    });
  }

  function updateResults() {
    const state = getFilterState();
    const sortedItems = sortItems(items, state.sort);
    let visibleCount = 0;

    sortedItems.forEach(function (item) {
      resultsContainer.appendChild(item);

      const title = item.dataset.title.toLowerCase();
      const category = item.dataset.category;
      const matchesSearch = title.includes(state.search);
      const matchesCategory = state.category === "all" || category === state.category;
      const isVisible = matchesSearch && matchesCategory;

      item.classList.toggle("is-hidden", !isVisible);

      if (isVisible) {
        visibleCount += 1;
      }
    });

    resultCount.textContent = visibleCount === 1 ? "1 result" : visibleCount + " results";
    filterStatus.textContent = "Saved filter: " + (state.category === "all" ? "all categories" : state.category) + " · " + state.sort;
    saveFilterState();
  }

  function resetFilters() {
    searchInput.value = "";
    categorySelect.value = "all";
    sortSelect.value = "popular";
    localStorage.removeItem(storageKey);
    updateResults();
  }

  searchInput.addEventListener("input", updateResults);
  categorySelect.addEventListener("change", updateResults);
  sortSelect.addEventListener("change", updateResults);
  resetButton.addEventListener("click", resetFilters);

  loadFilterState();
  updateResults();
})();

HTML

<div class="vb-storage-five-demo">
  <div class="vb-storage-five-directory" data-vb-storage-five>
    <div class="vb-storage-five-head">
      <span class="vb-storage-five-kicker">Example 05</span>
      <h3>Saved Search Filters</h3>
      <p>Search, filter, sort, refresh the page, and your selected directory filters will stay saved in localStorage.</p>
    </div>

    <div class="vb-storage-five-controls">
      <label>
        <span>Search resources</span>
        <input type="search" data-search placeholder="Search JavaScript, WordPress, SEO...">
      </label>

      <label>
        <span>Category</span>
        <select data-category>
          <option value="all">All categories</option>
          <option value="JavaScript">JavaScript</option>
          <option value="WordPress">WordPress</option>
          <option value="SEO">SEO</option>
        </select>
      </label>

      <label>
        <span>Sort by</span>
        <select data-sort>
          <option value="popular">Most popular</option>
          <option value="newest">Newest first</option>
          <option value="az">Name A-Z</option>
        </select>
      </label>

      <button type="button" data-reset-filters>Reset</button>
    </div>

    <div class="vb-storage-five-summary">
      <strong data-result-count>0 results</strong>
      <span data-filter-status>Filters are ready.</span>
    </div>

    <div class="vb-storage-five-results" data-results>
      <article data-item data-title="JavaScript LocalStorage Guide" data-category="JavaScript" data-date="2026-06-01" data-popularity="98">
        <span>JavaScript</span>
        <h4>JavaScript LocalStorage Guide</h4>
        <p>Save form data, carts, preferences, and UI state in the browser.</p>
      </article>

      <article data-item data-title="WordPress Custom Plugin Starter" data-category="WordPress" data-date="2026-05-12" data-popularity="91">
        <span>WordPress</span>
        <h4>WordPress Custom Plugin Starter</h4>
        <p>Build lightweight WordPress plugins with custom frontend features.</p>
      </article>

      <article data-item data-title="Entity SEO Checklist" data-category="SEO" data-date="2026-05-30" data-popularity="96">
        <span>SEO</span>
        <h4>Entity SEO Checklist</h4>
        <p>Improve structured data, internal links, and AI-readable content.</p>
      </article>

      <article data-item data-title="JavaScript Filter UI Examples" data-category="JavaScript" data-date="2026-04-20" data-popularity="89">
        <span>JavaScript</span>
        <h4>JavaScript Filter UI Examples</h4>
        <p>Create searchable and filterable user interfaces with vanilla JavaScript.</p>
      </article>

      <article data-item data-title="WooCommerce Product UX Tips" data-category="WordPress" data-date="2026-03-18" data-popularity="86">
        <span>WordPress</span>
        <h4>WooCommerce Product UX Tips</h4>
        <p>Improve product cards, checkout flows, quote forms, and mini carts.</p>
      </article>

      <article data-item data-title="Schema Markup for Blogs" data-category="SEO" data-date="2026-02-10" data-popularity="82">
        <span>SEO</span>
        <h4>Schema Markup for Blogs</h4>
        <p>Add useful structured data to long-form SEO content and guides.</p>
      </article>
    </div>
  </div>
</div>

CSS

.vb-storage-five-demo,
.vb-storage-five-demo * {
  box-sizing: border-box;
}

.vb-storage-five-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 36px;
  background:
    radial-gradient(circle at 14% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
    radial-gradient(circle at 84% 18%, rgba(236, 72, 153, 0.14), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #faf5ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.18);
  box-shadow: 0 28px 80px rgba(88, 28, 135, 0.10);
}

.vb-storage-five-directory {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-five-head {
  display: grid;
  gap: 12px;
  margin-bottom: 22px;
}

.vb-storage-five-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #f3e8ff;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-five-head h3 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-five-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-five-controls {
  display: grid;
  grid-template-columns: minmax(240px, 1fr) minmax(180px, 230px) minmax(180px, 230px) auto;
  gap: 12px;
  align-items: end;
  padding: 16px;
  border-radius: 24px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-five-controls label {
  display: grid;
  gap: 7px;
  min-width: 0;
}

.vb-storage-five-controls label span {
  color: #374151 !important;
  -webkit-text-fill-color: #374151 !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-five-controls input,
.vb-storage-five-controls select {
  width: 100%;
  min-height: 48px;
  padding: 0 14px;
  border: 1px solid rgba(148, 163, 184, 0.34);
  border-radius: 16px;
  background: #ffffff;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 14px;
  font-weight: 700;
  outline: none;
}

.vb-storage-five-controls input:focus,
.vb-storage-five-controls select:focus {
  border-color: rgba(147, 51, 234, 0.68);
  box-shadow: 0 0 0 4px rgba(147, 51, 234, 0.10);
}

.vb-storage-five-controls button {
  min-height: 48px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #7c3aed, #db2777);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-five-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin: 18px 0;
  padding: 13px 15px;
  border-radius: 18px;
  background: #faf5ff;
  border: 1px solid rgba(168, 85, 247, 0.18);
}

.vb-storage-five-summary strong {
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 15px;
  font-weight: 950;
}

.vb-storage-five-summary span {
  color: #6b21a8 !important;
  -webkit-text-fill-color: #6b21a8 !important;
  font-size: 13px;
  font-weight: 750;
}

.vb-storage-five-results {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}

.vb-storage-five-results article {
  display: grid;
  align-content: start;
  gap: 10px;
  min-height: 210px;
  padding: 18px;
  border-radius: 24px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 15px 38px rgba(15, 23, 42, 0.07);
}

.vb-storage-five-results article.is-hidden {
  display: none;
}

.vb-storage-five-results article span {
  display: inline-flex;
  justify-self: start;
  padding: 7px 10px;
  border-radius: 999px;
  background: #f3e8ff;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-storage-five-results article h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 21px !important;
  line-height: 1.15 !important;
  font-weight: 950 !important;
}

.vb-storage-five-results article p {
  margin: 0 !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
}

@media (max-width: 980px) {
  .vb-storage-five-controls {
    grid-template-columns: 1fr 1fr;
  }

  .vb-storage-five-results {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .vb-storage-five-controls,
  .vb-storage-five-results {
    grid-template-columns: 1fr;
  }

  .vb-storage-five-controls button {
    width: 100%;
  }

  .vb-storage-five-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This saved search filters localStorage example is useful for product archives, blog directories, resource libraries, job boards, real estate listings, course catalogs, SaaS dashboards, and searchable WordPress content sections.

6. LocalStorage To-Do List with Completed Tasks

A localStorage to-do list is one of the most searched JavaScript beginner projects, but it is also very useful for real UI features. The same logic can power saved checklists, task widgets, onboarding steps, dashboard notes, project lists, and progress trackers.

This example lets users add tasks, mark them complete, delete individual tasks, clear completed tasks, and keep the full task list saved after refresh. JavaScript stores the task array in localStorage and renders the list from saved browser data.

Example 06

Saved To-Do List

Add tasks and mark them complete. Your checklist is saved in localStorage and restored after page refresh.

Total 0
Done 0
Project Checklist

No tasks yet. Add your first task.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-six]");
  if (!wrapper) return;

  const storageKey = "vbStorageSixTasks";
  const form = wrapper.querySelector("[data-task-form]");
  const input = wrapper.querySelector("[data-task-input]");
  const taskList = wrapper.querySelector("[data-task-list]");
  const clearCompletedButton = wrapper.querySelector("[data-clear-completed]");
  const totalCount = wrapper.querySelector("[data-total-count]");
  const doneCount = wrapper.querySelector("[data-done-count]");

  let tasks = [];

  function saveTasks() {
    localStorage.setItem(storageKey, JSON.stringify(tasks));
  }

  function loadTasks() {
    const savedTasks = localStorage.getItem(storageKey);
    if (!savedTasks) return;

    try {
      const parsedTasks = JSON.parse(savedTasks);
      tasks = Array.isArray(parsedTasks) ? parsedTasks : [];
    } catch (error) {
      tasks = [];
      localStorage.removeItem(storageKey);
    }
  }

  function updateStats() {
    const completedTasks = tasks.filter(function (task) {
      return task.completed;
    });

    totalCount.textContent = tasks.length;
    doneCount.textContent = completedTasks.length;
  }

  function renderTasks() {
    taskList.innerHTML = "";

    if (tasks.length === 0) {
      taskList.innerHTML = '<p class="vb-storage-six-empty">No tasks yet. Add your first task.</p>';
      updateStats();
      return;
    }

    tasks.forEach(function (task) {
      const item = document.createElement("div");
      item.className = "vb-storage-six-task";
      item.classList.toggle("is-complete", task.completed);

      item.innerHTML =
        '<input type="checkbox" data-toggle-task="' + task.id + '"' + (task.completed ? " checked" : "") + '>' +
        '<span>' + task.text + '</span>' +
        '<button class="vb-storage-six-delete" type="button" data-delete-task="' + task.id + '">×</button>';

      taskList.appendChild(item);
    });

    updateStats();
  }

  function addTask(text) {
    tasks.unshift({
      id: String(Date.now()),
      text: text,
      completed: false
    });

    saveTasks();
    renderTasks();
  }

  function toggleTask(taskId) {
    tasks = tasks.map(function (task) {
      if (task.id === taskId) {
        return {
          id: task.id,
          text: task.text,
          completed: !task.completed
        };
      }

      return task;
    });

    saveTasks();
    renderTasks();
  }

  function deleteTask(taskId) {
    tasks = tasks.filter(function (task) {
      return task.id !== taskId;
    });

    saveTasks();
    renderTasks();
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();

    const taskText = input.value.trim();
    if (!taskText) return;

    addTask(taskText);
    input.value = "";
    input.focus();
  });

  taskList.addEventListener("click", function (event) {
    const toggleId = event.target.getAttribute("data-toggle-task");
    const deleteId = event.target.getAttribute("data-delete-task");

    if (toggleId) {
      toggleTask(toggleId);
    }

    if (deleteId) {
      deleteTask(deleteId);
    }
  });

  clearCompletedButton.addEventListener("click", function () {
    tasks = tasks.filter(function (task) {
      return !task.completed;
    });

    saveTasks();
    renderTasks();
  });

  loadTasks();
  renderTasks();
})();

HTML

<div class="vb-storage-six-demo">
  <div class="vb-storage-six-app" data-vb-storage-six>
    <div class="vb-storage-six-left">
      <span class="vb-storage-six-kicker">Example 06</span>
      <h3>Saved To-Do List</h3>
      <p>Add tasks and mark them complete. Your checklist is saved in localStorage and restored after page refresh.</p>

      <form class="vb-storage-six-form" data-task-form>
        <input type="text" data-task-input placeholder="Add a new task...">
        <button type="submit">Add Task</button>
      </form>

      <div class="vb-storage-six-stats">
        <div>
          <span>Total</span>
          <strong data-total-count>0</strong>
        </div>
        <div>
          <span>Done</span>
          <strong data-done-count>0</strong>
        </div>
      </div>
    </div>

    <div class="vb-storage-six-list-card">
      <div class="vb-storage-six-list-head">
        <strong>Project Checklist</strong>
        <button type="button" data-clear-completed>Clear Completed</button>
      </div>

      <div class="vb-storage-six-list" data-task-list>
        <p class="vb-storage-six-empty">No tasks yet. Add your first task.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-six-demo,
.vb-storage-six-demo * {
  box-sizing: border-box;
}

.vb-storage-six-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 18% 18%, rgba(245, 158, 11, 0.20), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(244, 63, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #fff1f2 55%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.22);
  box-shadow: 0 28px 80px rgba(146, 64, 14, 0.10);
}

.vb-storage-six-app {
  display: grid;
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
  gap: 22px;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #111827;
  box-shadow: 0 28px 90px rgba(17, 24, 39, 0.28);
}

.vb-storage-six-left,
.vb-storage-six-list-card {
  min-width: 0;
}

.vb-storage-six-left {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 18% 18%, rgba(251, 191, 36, 0.15), transparent 34%),
    rgba(255,255,255,0.07);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-six-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(251, 191, 36, 0.14);
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-six-left h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-six-left p {
  max-width: 540px;
  margin: 0 0 22px !important;
  color: #d1d5db !important;
  -webkit-text-fill-color: #d1d5db !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-six-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  margin-bottom: 18px;
}

.vb-storage-six-form input {
  min-width: 0;
  min-height: 50px;
  padding: 0 15px;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 750;
  outline: none;
}

.vb-storage-six-form input::placeholder {
  color: #9ca3af;
  -webkit-text-fill-color: #9ca3af;
}

.vb-storage-six-form input:focus {
  border-color: rgba(251, 191, 36, 0.70);
  box-shadow: 0 0 0 4px rgba(251, 191, 36, 0.12);
}

.vb-storage-six-form button,
.vb-storage-six-list-head button,
.vb-storage-six-delete {
  border: 0;
  cursor: pointer;
  font-weight: 950;
}

.vb-storage-six-form button {
  min-height: 50px;
  padding: 0 16px;
  border-radius: 999px;
  background: linear-gradient(135deg, #f59e0b, #f43f5e);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  box-shadow: 0 16px 38px rgba(244, 63, 94, 0.28);
}

.vb-storage-six-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-storage-six-stats div {
  padding: 16px;
  border-radius: 22px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-six-stats span {
  display: block;
  margin-bottom: 7px;
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-storage-six-stats strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 32px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-six-list-card {
  display: grid;
  gap: 14px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-six-list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.vb-storage-six-list-head strong {
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-six-list-head button {
  min-height: 40px;
  padding: 9px 13px;
  border-radius: 999px;
  background: #fff7ed;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 12px;
}

.vb-storage-six-list {
  display: grid;
  gap: 10px;
  min-height: 270px;
}

.vb-storage-six-empty {
  display: grid;
  place-items: center;
  min-height: 270px;
  margin: 0 !important;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  border-radius: 22px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-six-task {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 13px;
  border-radius: 18px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-six-task input {
  width: 20px;
  height: 20px;
  accent-color: #f59e0b;
}

.vb-storage-six-task span {
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 15px;
  line-height: 1.4;
  font-weight: 750;
  overflow-wrap: anywhere;
}

.vb-storage-six-task.is-complete span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  text-decoration: line-through;
}

.vb-storage-six-delete {
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 16px;
}

@media (max-width: 900px) {
  .vb-storage-six-app {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-six-form {
    grid-template-columns: 1fr;
  }

  .vb-storage-six-left h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-six-form button {
    width: 100%;
  }
}

This localStorage to-do list example is useful for JavaScript beginner projects, dashboard checklist widgets, onboarding task lists, project planning tools, saved notes, habit trackers, and lightweight browser-based productivity apps.

Need this on your website?

Can’t build it yourself? We can create it for you.

If you like these JavaScript localStorage features but want a custom version for your website, landing page, ecommerce store, or WordPress project, contact us and tell us what you need.

Contact Us

7. Favorite Products Saved with LocalStorage

A favorite products feature is useful for ecommerce stores, affiliate product lists, directories, comparison pages, SaaS template galleries, and digital product shops. Visitors can save items they like and return to the page later without losing their selected favorites.

This example lets users favorite and unfavorite product cards. JavaScript saves the selected favorite product IDs in localStorage, updates the heart buttons instantly, shows a saved favorites panel, and restores the same state after refresh.

Example 07

Saved Favorite Products

Click the heart buttons to save favorite products. Refresh the page and your selected products will still be marked.

SEO

Website SEO Audit

Technical SEO, content structure, speed, and schema review.

149€
WP

Custom WordPress Plugin

Lightweight custom plugin built for your exact website workflow.

399€
UI

Landing Page Section

Modern responsive section with custom HTML, CSS, and JavaScript.

99€
EC

Ecommerce Feature

Mini cart, product filter, quote form, or saved product feature.

249€

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-seven]");
  if (!wrapper) return;

  const storageKey = "vbStorageSevenFavorites";
  const productCards = Array.from(wrapper.querySelectorAll("[data-product-card]"));
  const favoriteButtons = wrapper.querySelectorAll("[data-favorite-button]");
  const favoriteList = wrapper.querySelector("[data-favorite-list]");
  const favoriteCount = wrapper.querySelector("[data-favorite-count]");
  const clearButton = wrapper.querySelector("[data-clear-favorites]");

  let favoriteIds = [];

  function saveFavorites() {
    localStorage.setItem(storageKey, JSON.stringify(favoriteIds));
  }

  function loadFavorites() {
    const savedFavorites = localStorage.getItem(storageKey);
    if (!savedFavorites) return;

    try {
      const parsedFavorites = JSON.parse(savedFavorites);
      favoriteIds = Array.isArray(parsedFavorites) ? parsedFavorites : [];
    } catch (error) {
      favoriteIds = [];
      localStorage.removeItem(storageKey);
    }
  }

  function getProductById(productId) {
    return productCards.find(function (card) {
      return card.getAttribute("data-id") === productId;
    });
  }

  function renderFavorites() {
    favoriteList.innerHTML = "";

    productCards.forEach(function (card) {
      const productId = card.getAttribute("data-id");
      const isFavorite = favoriteIds.includes(productId);
      const heart = card.querySelector("[data-favorite-button]");

      card.classList.toggle("is-favorite", isFavorite);
      heart.textContent = isFavorite ? "♥" : "♡";
    });

    favoriteCount.textContent = favoriteIds.length === 1 ? "1 saved" : favoriteIds.length + " saved";

    if (favoriteIds.length === 0) {
      favoriteList.innerHTML = '<p class="vb-storage-seven-empty">No favorites saved yet.</p>';
      return;
    }

    favoriteIds.forEach(function (productId) {
      const card = getProductById(productId);
      if (!card) return;

      const item = document.createElement("div");
      item.className = "vb-storage-seven-favorite-item";
      item.innerHTML =
        '<span>♥</span>' +
        '<strong>' + card.getAttribute("data-name") + '</strong>';

      favoriteList.appendChild(item);
    });
  }

  function toggleFavorite(productId) {
    if (favoriteIds.includes(productId)) {
      favoriteIds = favoriteIds.filter(function (id) {
        return id !== productId;
      });
    } else {
      favoriteIds.push(productId);
    }

    saveFavorites();
    renderFavorites();
  }

  favoriteButtons.forEach(function (button) {
    button.addEventListener("click", function () {
      const card = button.closest("[data-product-card]");
      toggleFavorite(card.getAttribute("data-id"));
    });
  });

  clearButton.addEventListener("click", function () {
    favoriteIds = [];
    saveFavorites();
    renderFavorites();
  });

  loadFavorites();
  renderFavorites();
})();

HTML

<div class="vb-storage-seven-demo">
  <div class="vb-storage-seven-wrap" data-vb-storage-seven>
    <div class="vb-storage-seven-head">
      <span class="vb-storage-seven-kicker">Example 07</span>
      <h3>Saved Favorite Products</h3>
      <p>Click the heart buttons to save favorite products. Refresh the page and your selected products will still be marked.</p>
    </div>

    <div class="vb-storage-seven-layout">
      <div class="vb-storage-seven-products">
        <article class="vb-storage-seven-card" data-product-card data-id="website-audit" data-name="Website SEO Audit">
          <button type="button" class="vb-storage-seven-heart" data-favorite-button aria-label="Save favorite">♡</button>
          <div class="vb-storage-seven-icon">SEO</div>
          <h4>Website SEO Audit</h4>
          <p>Technical SEO, content structure, speed, and schema review.</p>
          <strong>149€</strong>
        </article>

        <article class="vb-storage-seven-card" data-product-card data-id="plugin-build" data-name="Custom WordPress Plugin">
          <button type="button" class="vb-storage-seven-heart" data-favorite-button aria-label="Save favorite">♡</button>
          <div class="vb-storage-seven-icon">WP</div>
          <h4>Custom WordPress Plugin</h4>
          <p>Lightweight custom plugin built for your exact website workflow.</p>
          <strong>399€</strong>
        </article>

        <article class="vb-storage-seven-card" data-product-card data-id="landing-section" data-name="Landing Page Section">
          <button type="button" class="vb-storage-seven-heart" data-favorite-button aria-label="Save favorite">♡</button>
          <div class="vb-storage-seven-icon">UI</div>
          <h4>Landing Page Section</h4>
          <p>Modern responsive section with custom HTML, CSS, and JavaScript.</p>
          <strong>99€</strong>
        </article>

        <article class="vb-storage-seven-card" data-product-card data-id="shop-feature" data-name="Ecommerce Feature">
          <button type="button" class="vb-storage-seven-heart" data-favorite-button aria-label="Save favorite">♡</button>
          <div class="vb-storage-seven-icon">EC</div>
          <h4>Ecommerce Feature</h4>
          <p>Mini cart, product filter, quote form, or saved product feature.</p>
          <strong>249€</strong>
        </article>
      </div>

      <aside class="vb-storage-seven-panel">
        <div class="vb-storage-seven-panel-head">
          <strong>Saved Favorites</strong>
          <span data-favorite-count>0 saved</span>
        </div>

        <div class="vb-storage-seven-list" data-favorite-list>
          <p class="vb-storage-seven-empty">No favorites saved yet.</p>
        </div>

        <button type="button" data-clear-favorites>Clear Favorites</button>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-storage-seven-demo,
.vb-storage-seven-demo * {
  box-sizing: border-box;
}

.vb-storage-seven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 46px;
  background:
    radial-gradient(circle at 12% 18%, rgba(244, 63, 94, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(168, 85, 247, 0.16), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #faf5ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(244, 63, 94, 0.18);
  box-shadow: 0 28px 80px rgba(159, 18, 57, 0.10);
}

.vb-storage-seven-wrap {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-seven-head {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-seven-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-seven-head h3 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-seven-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-seven-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
  gap: 20px;
  align-items: start;
}

.vb-storage-seven-products {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.vb-storage-seven-card {
  position: relative;
  display: grid;
  align-content: start;
  gap: 12px;
  min-height: 270px;
  padding: 18px;
  border-radius: 26px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.24);
  transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
}

.vb-storage-seven-card.is-favorite {
  border-color: rgba(244, 63, 94, 0.52);
  background: linear-gradient(135deg, #fff1f2, #ffffff) !important;
  box-shadow: 0 18px 46px rgba(244, 63, 94, 0.12);
}

.vb-storage-seven-card:hover {
  transform: translateY(-3px);
}

.vb-storage-seven-heart {
  position: absolute;
  top: 16px;
  right: 16px;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 25px;
  line-height: 1;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 12px 28px rgba(15, 23, 42, 0.10);
}

.vb-storage-seven-card.is-favorite .vb-storage-seven-heart {
  background: #e11d48;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-seven-icon {
  display: grid;
  place-items: center;
  width: 74px;
  height: 74px;
  border-radius: 24px;
  background: linear-gradient(135deg, #e11d48, #9333ea);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
}

.vb-storage-seven-card h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
}

.vb-storage-seven-card p {
  margin: 0 !important;
  color: #4b5563 !important;
  -webkit-text-fill-color: #4b5563 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
}

.vb-storage-seven-card strong {
  align-self: end;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 24px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-seven-panel {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 28px;
  background: #111827;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.24);
}

.vb-storage-seven-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-seven-panel-head strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  font-weight: 950;
}

.vb-storage-seven-panel-head span {
  color: #fecdd3 !important;
  -webkit-text-fill-color: #fecdd3 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-seven-list {
  display: grid;
  gap: 10px;
  min-height: 250px;
}

.vb-storage-seven-empty {
  display: grid;
  place-items: center;
  min-height: 250px;
  margin: 0 !important;
  border: 1px dashed rgba(255,255,255,0.22);
  border-radius: 20px;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-seven-favorite-item {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 12px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-seven-favorite-item span {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 15px;
  background: linear-gradient(135deg, #e11d48, #9333ea);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
}

.vb-storage-seven-favorite-item strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.2;
  font-weight: 900;
}

.vb-storage-seven-panel > button {
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #e11d48, #9333ea);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

@media (max-width: 940px) {
  .vb-storage-seven-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-seven-products {
    grid-template-columns: 1fr;
  }

  .vb-storage-seven-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This favorite products localStorage example is useful for ecommerce stores, affiliate websites, product comparison pages, service package lists, saved template galleries, and WooCommerce-style product cards.

8. Multi-Step Form Progress Saved with LocalStorage

Saving multi-step form progress is very useful for quote forms, onboarding flows, checkout forms, application forms, booking forms, and lead generation pages. It prevents users from losing their progress when they refresh the page or come back later.

This example saves the active step and form values in localStorage. Visitors can move between steps, fill in fields, refresh the page, and continue from the same step with the same saved answers.

Example 08

Saved Multi-Step Form

Fill out the steps, refresh the page, and your form progress will be restored from localStorage.

Project Basics

Budget and Timeline

Final Notes

Step 1 of 3

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-eight]");
  if (!wrapper) return;

  const storageKey = "vbStorageEightMultiStepForm";
  const steps = Array.from(wrapper.querySelectorAll("[data-step]"));
  const form = wrapper.querySelector(".vb-storage-eight-form");
  const fields = Array.from(form.querySelectorAll("input, select, textarea"));
  const previousButton = wrapper.querySelector("[data-prev-step]");
  const nextButton = wrapper.querySelector("[data-next-step]");
  const resetButton = wrapper.querySelector("[data-reset-form]");
  const stepLabel = wrapper.querySelector("[data-step-label]");
  const progressLine = wrapper.querySelector("[data-progress-line]");

  let currentStep = 0;

  function getFormData() {
    const data = {};

    fields.forEach(function (field) {
      data[field.name] = field.value;
    });

    return data;
  }

  function saveProgress() {
    localStorage.setItem(storageKey, JSON.stringify({
      currentStep: currentStep,
      formData: getFormData()
    }));
  }

  function loadProgress() {
    const savedProgress = localStorage.getItem(storageKey);
    if (!savedProgress) return;

    try {
      const progress = JSON.parse(savedProgress);
      currentStep = Number(progress.currentStep) || 0;

      if (progress.formData) {
        fields.forEach(function (field) {
          field.value = progress.formData[field.name] || "";
        });
      }
    } catch (error) {
      currentStep = 0;
      localStorage.removeItem(storageKey);
    }
  }

  function showStep(stepIndex) {
    currentStep = Math.max(0, Math.min(stepIndex, steps.length - 1));

    steps.forEach(function (step, index) {
      step.classList.toggle("is-active", index === currentStep);
    });

    previousButton.disabled = currentStep === 0;
    nextButton.textContent = currentStep === steps.length - 1 ? "Finish" : "Next";
    stepLabel.textContent = "Step " + (currentStep + 1) + " of " + steps.length;
    progressLine.style.width = ((currentStep + 1) / steps.length * 100) + "%";

    saveProgress();
  }

  fields.forEach(function (field) {
    field.addEventListener("input", saveProgress);
    field.addEventListener("change", saveProgress);
  });

  previousButton.addEventListener("click", function () {
    showStep(currentStep - 1);
  });

  nextButton.addEventListener("click", function () {
    if (currentStep < steps.length - 1) {
      showStep(currentStep + 1);
    } else {
      saveProgress();
      stepLabel.textContent = "Progress saved in localStorage";
    }
  });

  resetButton.addEventListener("click", function () {
    localStorage.removeItem(storageKey);
    form.reset();
    currentStep = 0;
    showStep(0);
  });

  loadProgress();
  showStep(currentStep);
})();

HTML

<div class="vb-storage-eight-demo">
  <div class="vb-storage-eight-formbox" data-vb-storage-eight>
    <div class="vb-storage-eight-intro">
      <span class="vb-storage-eight-kicker">Example 08</span>
      <h3>Saved Multi-Step Form</h3>
      <p>Fill out the steps, refresh the page, and your form progress will be restored from localStorage.</p>
    </div>

    <div class="vb-storage-eight-progress">
      <span data-progress-line></span>
    </div>

    <form class="vb-storage-eight-form">
      <section class="vb-storage-eight-step is-active" data-step="0">
        <h4>Project Basics</h4>
        <label>
          <span>Project name</span>
          <input type="text" name="projectName" placeholder="New website project">
        </label>
        <label>
          <span>Project type</span>
          <select name="projectType">
            <option value="">Choose type</option>
            <option value="Website">Website</option>
            <option value="Ecommerce">Ecommerce</option>
            <option value="WordPress Plugin">WordPress Plugin</option>
          </select>
        </label>
      </section>

      <section class="vb-storage-eight-step" data-step="1">
        <h4>Budget and Timeline</h4>
        <label>
          <span>Estimated budget</span>
          <select name="budget">
            <option value="">Choose budget</option>
            <option value="500-1000€">500-1000€</option>
            <option value="1000-3000€">1000-3000€</option>
            <option value="3000€+">3000€+</option>
          </select>
        </label>
        <label>
          <span>Timeline</span>
          <input type="text" name="timeline" placeholder="Example: 2-4 weeks">
        </label>
      </section>

      <section class="vb-storage-eight-step" data-step="2">
        <h4>Final Notes</h4>
        <label>
          <span>What should be included?</span>
          <textarea name="notes" rows="5" placeholder="Describe the features, pages, design style, or functionality..."></textarea>
        </label>
      </section>

      <div class="vb-storage-eight-actions">
        <button type="button" data-prev-step>Back</button>
        <span data-step-label>Step 1 of 3</span>
        <button type="button" data-next-step>Next</button>
      </div>

      <button class="vb-storage-eight-reset" type="button" data-reset-form>Reset Saved Progress</button>
    </form>
  </div>
</div>

CSS

.vb-storage-eight-demo,
.vb-storage-eight-demo * {
  box-sizing: border-box;
}

.vb-storage-eight-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 42px;
  background:
    radial-gradient(circle at 12% 18%, rgba(37, 99, 235, 0.18), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(20, 184, 166, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #f0fdfa 54%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-storage-eight-formbox {
  max-width: 920px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.11);
}

.vb-storage-eight-intro {
  display: grid;
  gap: 12px;
  margin-bottom: 22px;
}

.vb-storage-eight-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-eight-intro h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-eight-intro p {
  max-width: 720px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-eight-progress {
  height: 12px;
  overflow: hidden;
  margin-bottom: 20px;
  border-radius: 999px;
  background: #e2e8f0;
}

.vb-storage-eight-progress span {
  display: block;
  width: 33.33%;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #14b8a6);
  transition: width 0.25s ease;
}

.vb-storage-eight-form {
  display: grid;
  gap: 16px;
}

.vb-storage-eight-step {
  display: none;
  gap: 14px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 26px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-eight-step.is-active {
  display: grid;
}

.vb-storage-eight-step h4 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 28px !important;
  line-height: 1.15 !important;
  font-weight: 950 !important;
}

.vb-storage-eight-step label {
  display: grid;
  gap: 7px;
}

.vb-storage-eight-step label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-eight-step input,
.vb-storage-eight-step select,
.vb-storage-eight-step textarea {
  width: 100%;
  border: 1px solid rgba(148, 163, 184, 0.34);
  border-radius: 16px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 650;
  outline: none;
}

.vb-storage-eight-step input,
.vb-storage-eight-step select {
  min-height: 50px;
  padding: 0 14px;
}

.vb-storage-eight-step textarea {
  resize: vertical;
  padding: 13px 14px;
  line-height: 1.55;
}

.vb-storage-eight-step input:focus,
.vb-storage-eight-step select:focus,
.vb-storage-eight-step textarea:focus {
  border-color: rgba(37, 99, 235, 0.74);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.10);
}

.vb-storage-eight-actions {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
}

.vb-storage-eight-actions button,
.vb-storage-eight-reset {
  border: 0;
  cursor: pointer;
  font-weight: 950;
}

.vb-storage-eight-actions button {
  min-height: 46px;
  padding: 0 16px;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 13px;
}

.vb-storage-eight-actions button:last-child {
  background: linear-gradient(135deg, #2563eb, #14b8a6);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-eight-actions span {
  text-align: center;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-eight-reset {
  justify-self: start;
  min-height: 42px;
  padding: 0 14px;
  border-radius: 999px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 12px;
}

@media (max-width: 560px) {
  .vb-storage-eight-intro h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-eight-actions {
    grid-template-columns: 1fr;
  }

  .vb-storage-eight-actions span {
    order: -1;
  }

  .vb-storage-eight-actions button,
  .vb-storage-eight-reset {
    width: 100%;
  }
}

This localStorage multi-step form example is useful for quote request forms, checkout forms, onboarding flows, booking forms, lead generation pages, application forms, and longer WordPress contact forms.

9. Dismissible Banner Saved with LocalStorage

A dismissible banner saved with localStorage is useful for cookie notices, announcement bars, discount banners, onboarding messages, update notices, and promotional website messages. Once the visitor closes the banner, localStorage can remember that choice.

This example shows a floating announcement banner with close and reset controls. JavaScript stores the dismissed state in localStorage, hides the banner after closing, and keeps it hidden after refresh until the demo reset button is used.

Example 09

Dismissible Saved Banner

Close the banner and refresh the page. localStorage remembers that this message was dismissed.

Website Content Area

The announcement banner appears inside this demo browser until the visitor closes it.

New website feature available

Use localStorage to remember dismissed banners, cookie notices, and promo messages.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-nine]");
  if (!wrapper) return;

  const storageKey = "vbStorageNineBannerDismissed";
  const banner = wrapper.querySelector("[data-banner]");
  const closeButton = wrapper.querySelector("[data-close-banner]");
  const resetButton = wrapper.querySelector("[data-reset-banner]");
  const status = wrapper.querySelector("[data-banner-status]");

  function updateBanner() {
    const isDismissed = localStorage.getItem(storageKey) === "true";

    banner.classList.toggle("is-hidden", isDismissed);
    status.textContent = isDismissed
      ? "Banner status: hidden by localStorage"
      : "Banner status: visible";
  }

  closeButton.addEventListener("click", function () {
    localStorage.setItem(storageKey, "true");
    updateBanner();
  });

  resetButton.addEventListener("click", function () {
    localStorage.removeItem(storageKey);
    updateBanner();
  });

  updateBanner();
})();

HTML

<div class="vb-storage-nine-demo">
  <div class="vb-storage-nine-stage" data-vb-storage-nine>
    <div class="vb-storage-nine-page">
      <span class="vb-storage-nine-kicker">Example 09</span>
      <h3>Dismissible Saved Banner</h3>
      <p>Close the banner and refresh the page. localStorage remembers that this message was dismissed.</p>

      <div class="vb-storage-nine-browser">
        <div class="vb-storage-nine-browser-top">
          <span></span>
          <span></span>
          <span></span>
        </div>

        <div class="vb-storage-nine-hero">
          <strong>Website Content Area</strong>
          <p>The announcement banner appears inside this demo browser until the visitor closes it.</p>
        </div>

        <div class="vb-storage-nine-banner" data-banner>
          <div>
            <strong>New website feature available</strong>
            <p>Use localStorage to remember dismissed banners, cookie notices, and promo messages.</p>
          </div>
          <button type="button" data-close-banner>Close</button>
        </div>
      </div>

      <div class="vb-storage-nine-footer">
        <span data-banner-status>Banner status: visible</span>
        <button type="button" data-reset-banner>Show Banner Again</button>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-nine-demo,
.vb-storage-nine-demo * {
  box-sizing: border-box;
}

.vb-storage-nine-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 12% 18%, rgba(14, 165, 233, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(245, 158, 11, 0.16), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #fffbeb 54%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  box-shadow: 0 28px 80px rgba(12, 74, 110, 0.10);
}

.vb-storage-nine-stage {
  max-width: 1080px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #0f172a;
  box-shadow: 0 28px 90px rgba(15, 23, 42, 0.30);
}

.vb-storage-nine-page {
  display: grid;
  gap: 16px;
}

.vb-storage-nine-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(14, 165, 233, 0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-nine-page h3 {
  max-width: 760px;
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-nine-page > p {
  max-width: 760px;
  margin: 0 0 8px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-nine-browser {
  position: relative;
  min-height: 390px;
  overflow: hidden;
  border-radius: 28px;
  background: linear-gradient(135deg, #f8fafc, #e0f2fe) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.24);
}

.vb-storage-nine-browser-top {
  display: flex;
  gap: 8px;
  padding: 16px;
  background: #ffffff;
  border-bottom: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-storage-nine-browser-top span {
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: #f87171;
}

.vb-storage-nine-browser-top span:nth-child(2) {
  background: #fbbf24;
}

.vb-storage-nine-browser-top span:nth-child(3) {
  background: #34d399;
}

.vb-storage-nine-hero {
  display: grid;
  place-items: center;
  align-content: center;
  min-height: 300px;
  padding: 30px;
  text-align: center;
}

.vb-storage-nine-hero strong {
  margin-bottom: 10px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(28px, 5vw, 48px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.055em;
}

.vb-storage-nine-hero p {
  max-width: 480px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-storage-nine-banner {
  position: absolute;
  left: 18px;
  right: 18px;
  bottom: 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 16px;
  border-radius: 22px;
  background: #0f172a;
  border: 1px solid rgba(255,255,255,0.12);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.28);
}

.vb-storage-nine-banner.is-hidden {
  display: none;
}

.vb-storage-nine-banner strong {
  display: block;
  margin-bottom: 5px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 17px;
  line-height: 1.2;
  font-weight: 950;
}

.vb-storage-nine-banner p {
  margin: 0 !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 13px;
  line-height: 1.45;
  font-weight: 650;
}

.vb-storage-nine-banner button,
.vb-storage-nine-footer button {
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  font-weight: 950;
  white-space: nowrap;
}

.vb-storage-nine-banner button {
  min-height: 42px;
  padding: 10px 14px;
  background: linear-gradient(135deg, #0ea5e9, #f59e0b);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
}

.vb-storage-nine-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 16px;
  border-radius: 20px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-nine-footer span {
  color: #e2e8f0 !important;
  -webkit-text-fill-color: #e2e8f0 !important;
  font-size: 14px;
  font-weight: 800;
}

.vb-storage-nine-footer button {
  min-height: 40px;
  padding: 9px 13px;
  background: #ffffff;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 12px;
}

@media (max-width: 640px) {
  .vb-storage-nine-page h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-nine-banner {
    align-items: stretch;
    flex-direction: column;
  }

  .vb-storage-nine-banner button,
  .vb-storage-nine-footer button {
    width: 100%;
  }
}

This dismissible banner localStorage example is useful for cookie notices, announcement bars, promo banners, onboarding messages, discount bars, website update alerts, and custom WordPress notification sections.

10. LocalStorage Notes App

A localStorage notes app is one of the most useful JavaScript projects because it teaches saving, editing, deleting, rendering, and restoring user-generated content. The same logic can be used for dashboard notes, admin reminders, customer notes, private browser notes, and lightweight project planning widgets.

This example lets users create notes, edit existing notes, delete notes, and keep all notes saved after refresh. JavaScript stores an array of note objects in localStorage and rebuilds the note cards when the page loads again.

Example 10

Saved Notes App

Create notes, edit them, delete them, and refresh the page. Your notes stay saved in localStorage.

Saved Notes 0 notes

No notes saved yet.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-ten]");
  if (!wrapper) return;

  const storageKey = "vbStorageTenNotes";
  const form = wrapper.querySelector("[data-note-form]");
  const titleInput = wrapper.querySelector("[data-note-title]");
  const textInput = wrapper.querySelector("[data-note-text]");
  const submitButton = wrapper.querySelector("[data-submit-note]");
  const cancelEditButton = wrapper.querySelector("[data-cancel-edit]");
  const noteList = wrapper.querySelector("[data-note-list]");
  const noteCount = wrapper.querySelector("[data-note-count]");

  let notes = [];
  let editingId = null;

  function saveNotes() {
    localStorage.setItem(storageKey, JSON.stringify(notes));
  }

  function loadNotes() {
    const savedNotes = localStorage.getItem(storageKey);
    if (!savedNotes) return;

    try {
      const parsedNotes = JSON.parse(savedNotes);
      notes = Array.isArray(parsedNotes) ? parsedNotes : [];
    } catch (error) {
      notes = [];
      localStorage.removeItem(storageKey);
    }
  }

  function resetForm() {
    editingId = null;
    form.reset();
    submitButton.textContent = "Add Note";
    cancelEditButton.hidden = true;
  }

  function renderNotes() {
    noteList.innerHTML = "";

    noteCount.textContent = notes.length === 1 ? "1 note" : notes.length + " notes";

    if (notes.length === 0) {
      noteList.innerHTML = '<p class="vb-storage-ten-empty">No notes saved yet.</p>';
      return;
    }

    notes.forEach(function (note) {
      const card = document.createElement("article");
      card.className = "vb-storage-ten-note";

      card.innerHTML =
        '<h4>' + note.title + '</h4>' +
        '<p>' + note.text + '</p>' +
        '<small>Saved: ' + note.createdAt + '</small>' +
        '<div class="vb-storage-ten-note-actions">' +
          '<button type="button" data-edit-note="' + note.id + '">Edit</button>' +
          '<button type="button" data-delete-note="' + note.id + '">Delete</button>' +
        '</div>';

      noteList.appendChild(card);
    });
  }

  function addOrUpdateNote() {
    const title = titleInput.value.trim();
    const text = textInput.value.trim();

    if (!title || !text) return;

    if (editingId) {
      notes = notes.map(function (note) {
        if (note.id === editingId) {
          return {
            id: note.id,
            title: title,
            text: text,
            createdAt: note.createdAt
          };
        }

        return note;
      });
    } else {
      notes.unshift({
        id: String(Date.now()),
        title: title,
        text: text,
        createdAt: new Date().toLocaleDateString()
      });
    }

    saveNotes();
    renderNotes();
    resetForm();
  }

  function startEditing(noteId) {
    const note = notes.find(function (item) {
      return item.id === noteId;
    });

    if (!note) return;

    editingId = note.id;
    titleInput.value = note.title;
    textInput.value = note.text;
    submitButton.textContent = "Update Note";
    cancelEditButton.hidden = false;
    titleInput.focus();
  }

  function deleteNote(noteId) {
    notes = notes.filter(function (note) {
      return note.id !== noteId;
    });

    if (editingId === noteId) {
      resetForm();
    }

    saveNotes();
    renderNotes();
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();
    addOrUpdateNote();
  });

  noteList.addEventListener("click", function (event) {
    const editId = event.target.getAttribute("data-edit-note");
    const deleteId = event.target.getAttribute("data-delete-note");

    if (editId) startEditing(editId);
    if (deleteId) deleteNote(deleteId);
  });

  cancelEditButton.addEventListener("click", resetForm);

  loadNotes();
  renderNotes();
})();

HTML

<div class="vb-storage-ten-demo">
  <div class="vb-storage-ten-app" data-vb-storage-ten>
    <div class="vb-storage-ten-left">
      <span class="vb-storage-ten-kicker">Example 10</span>
      <h3>Saved Notes App</h3>
      <p>Create notes, edit them, delete them, and refresh the page. Your notes stay saved in localStorage.</p>

      <form class="vb-storage-ten-form" data-note-form>
        <input type="text" data-note-title placeholder="Note title">
        <textarea data-note-text rows="5" placeholder="Write your note here..."></textarea>
        <button type="submit" data-submit-note>Add Note</button>
      </form>

      <button type="button" class="vb-storage-ten-cancel" data-cancel-edit hidden>Cancel Edit</button>
    </div>

    <div class="vb-storage-ten-board">
      <div class="vb-storage-ten-board-head">
        <strong>Saved Notes</strong>
        <span data-note-count>0 notes</span>
      </div>

      <div class="vb-storage-ten-list" data-note-list>
        <p class="vb-storage-ten-empty">No notes saved yet.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-ten-demo,
.vb-storage-ten-demo * {
  box-sizing: border-box;
}

.vb-storage-ten-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(234, 179, 8, 0.18), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(59, 130, 246, 0.15), transparent 34%),
    linear-gradient(135deg, #fefce8 0%, #eff6ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(234, 179, 8, 0.22);
  box-shadow: 0 28px 80px rgba(113, 63, 18, 0.10);
}

.vb-storage-ten-app {
  display: grid;
  grid-template-columns: minmax(0, 0.84fr) minmax(0, 1.16fr);
  gap: 22px;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-ten-left,
.vb-storage-ten-board {
  min-width: 0;
}

.vb-storage-ten-left {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #111827;
}

.vb-storage-ten-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(250, 204, 21, 0.16);
  color: #fef08a !important;
  -webkit-text-fill-color: #fef08a !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-ten-left h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-ten-left p {
  max-width: 520px;
  margin: 0 0 22px !important;
  color: #d1d5db !important;
  -webkit-text-fill-color: #d1d5db !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-ten-form {
  display: grid;
  gap: 12px;
}

.vb-storage-ten-form input,
.vb-storage-ten-form textarea {
  width: 100%;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 18px;
  background: rgba(255,255,255,0.09);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 700;
  outline: none;
}

.vb-storage-ten-form input {
  min-height: 50px;
  padding: 0 15px;
}

.vb-storage-ten-form textarea {
  resize: vertical;
  padding: 14px 15px;
  line-height: 1.55;
}

.vb-storage-ten-form input::placeholder,
.vb-storage-ten-form textarea::placeholder {
  color: #9ca3af;
  -webkit-text-fill-color: #9ca3af;
}

.vb-storage-ten-form input:focus,
.vb-storage-ten-form textarea:focus {
  border-color: rgba(250, 204, 21, 0.72);
  box-shadow: 0 0 0 4px rgba(250, 204, 21, 0.12);
}

.vb-storage-ten-form button,
.vb-storage-ten-cancel,
.vb-storage-ten-note-actions button {
  border: 0;
  cursor: pointer;
  font-weight: 950;
}

.vb-storage-ten-form button {
  min-height: 50px;
  border-radius: 999px;
  background: linear-gradient(135deg, #eab308, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.22);
}

.vb-storage-ten-cancel {
  align-self: flex-start;
  min-height: 40px;
  margin-top: 10px;
  padding: 0 13px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
}

.vb-storage-ten-board {
  display: grid;
  gap: 14px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-ten-board-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-ten-board-head strong {
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-ten-board-head span {
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-ten-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  min-height: 330px;
}

.vb-storage-ten-empty {
  grid-column: 1 / -1;
  display: grid;
  place-items: center;
  min-height: 330px;
  margin: 0 !important;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  border-radius: 22px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-ten-note {
  display: grid;
  align-content: start;
  gap: 10px;
  min-height: 190px;
  padding: 16px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.07);
}

.vb-storage-ten-note h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 19px !important;
  line-height: 1.18 !important;
  font-weight: 950 !important;
}

.vb-storage-ten-note p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-storage-ten-note small {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 750;
}

.vb-storage-ten-note-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: auto;
}

.vb-storage-ten-note-actions button {
  min-height: 34px;
  padding: 7px 11px;
  border-radius: 999px;
  font-size: 12px;
}

.vb-storage-ten-note-actions button:first-child {
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
}

.vb-storage-ten-note-actions button:last-child {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

@media (max-width: 920px) {
  .vb-storage-ten-app {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-ten-list {
    grid-template-columns: 1fr;
  }

  .vb-storage-ten-left h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This localStorage notes app example is useful for JavaScript notes projects, dashboard widgets, admin reminders, customer notes, browser-based writing tools, and lightweight project planning interfaces.

11. Active Tab Saved with LocalStorage

Saving the active tab with localStorage is useful for dashboards, pricing sections, product details, account settings, documentation pages, profile areas, and long content sections. It lets the interface reopen the same tab after refresh instead of always starting from the first tab.

This example has three content tabs. JavaScript saves the selected tab key in localStorage, updates the active tab instantly, and restores the same tab when the page loads again.

Example 11

Saved Active Tab

Choose a tab and refresh the page. localStorage restores the same active tab automatically.

01

Overview Tab

This panel could contain product details, account information, dashboard stats, or documentation summary content.

  • Good for default content
  • Restores after refresh
  • Stores only the selected tab key
02

Features Tab

This panel can show feature lists, pricing details, comparison content, or product specifications.

  • Great for product pages
  • Useful for SaaS sections
  • Simple localStorage state
03

Settings Tab

This panel is useful for user preferences, dashboard controls, display options, and saved interface settings.

  • Useful for dashboards
  • Works after page reload
  • Easy to customize
Current saved tab: overview

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-eleven]");
  if (!wrapper) return;

  const storageKey = "vbStorageElevenActiveTab";
  const buttons = wrapper.querySelectorAll("[data-tab-button]");
  const panels = wrapper.querySelectorAll("[data-tab-panel]");
  const currentTab = wrapper.querySelector("[data-current-tab]");

  function activateTab(tabKey) {
    buttons.forEach(function (button) {
      button.classList.toggle("is-active", button.getAttribute("data-tab-button") === tabKey);
    });

    panels.forEach(function (panel) {
      panel.classList.toggle("is-active", panel.getAttribute("data-tab-panel") === tabKey);
    });

    currentTab.textContent = tabKey;
    localStorage.setItem(storageKey, tabKey);
  }

  buttons.forEach(function (button) {
    button.addEventListener("click", function () {
      activateTab(button.getAttribute("data-tab-button"));
    });
  });

  const savedTab = localStorage.getItem(storageKey) || "overview";
  activateTab(savedTab);
})();

HTML

<div class="vb-storage-eleven-demo">
  <div class="vb-storage-eleven-tabs" data-vb-storage-eleven>
    <div class="vb-storage-eleven-header">
      <span class="vb-storage-eleven-kicker">Example 11</span>
      <h3>Saved Active Tab</h3>
      <p>Choose a tab and refresh the page. localStorage restores the same active tab automatically.</p>
    </div>

    <div class="vb-storage-eleven-shell">
      <div class="vb-storage-eleven-nav" role="tablist">
        <button type="button" data-tab-button="overview" class="is-active">Overview</button>
        <button type="button" data-tab-button="features">Features</button>
        <button type="button" data-tab-button="settings">Settings</button>
      </div>

      <div class="vb-storage-eleven-content">
        <section data-tab-panel="overview" class="is-active">
          <span>01</span>
          <h4>Overview Tab</h4>
          <p>This panel could contain product details, account information, dashboard stats, or documentation summary content.</p>
          <ul>
            <li>Good for default content</li>
            <li>Restores after refresh</li>
            <li>Stores only the selected tab key</li>
          </ul>
        </section>

        <section data-tab-panel="features">
          <span>02</span>
          <h4>Features Tab</h4>
          <p>This panel can show feature lists, pricing details, comparison content, or product specifications.</p>
          <ul>
            <li>Great for product pages</li>
            <li>Useful for SaaS sections</li>
            <li>Simple localStorage state</li>
          </ul>
        </section>

        <section data-tab-panel="settings">
          <span>03</span>
          <h4>Settings Tab</h4>
          <p>This panel is useful for user preferences, dashboard controls, display options, and saved interface settings.</p>
          <ul>
            <li>Useful for dashboards</li>
            <li>Works after page reload</li>
            <li>Easy to customize</li>
          </ul>
        </section>
      </div>

      <div class="vb-storage-eleven-status">
        Current saved tab: <strong data-current-tab>overview</strong>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-eleven-demo,
.vb-storage-eleven-demo * {
  box-sizing: border-box;
}

.vb-storage-eleven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 14% 18%, rgba(20, 184, 166, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdfa 0%, #eef2ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.18);
  box-shadow: 0 28px 80px rgba(15, 118, 110, 0.10);
}

.vb-storage-eleven-tabs {
  max-width: 1060px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-eleven-header {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-eleven-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-eleven-header h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-eleven-header p {
  max-width: 760px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-eleven-shell {
  display: grid;
  gap: 16px;
  padding: 18px;
  border-radius: 28px;
  background: #0f172a;
}

.vb-storage-eleven-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.vb-storage-eleven-nav button {
  min-height: 44px;
  padding: 10px 15px;
  border: 0;
  border-radius: 999px;
  background: rgba(255,255,255,0.09);
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-eleven-nav button.is-active {
  background: linear-gradient(135deg, #14b8a6, #6366f1);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 16px 34px rgba(20, 184, 166, 0.22);
}

.vb-storage-eleven-content {
  min-height: 330px;
}

.vb-storage-eleven-content section {
  display: none;
  min-height: 330px;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 24px;
  background:
    radial-gradient(circle at 85% 15%, rgba(20, 184, 166, 0.16), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
}

.vb-storage-eleven-content section.is-active {
  display: grid;
  align-content: center;
  gap: 14px;
}

.vb-storage-eleven-content section span {
  display: inline-grid;
  place-items: center;
  width: 58px;
  height: 58px;
  border-radius: 18px;
  background: linear-gradient(135deg, #14b8a6, #6366f1);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
}

.vb-storage-eleven-content section h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(28px, 4vw, 44px) !important;
  line-height: 1.05 !important;
  font-weight: 950 !important;
  letter-spacing: -0.055em;
}

.vb-storage-eleven-content section p {
  max-width: 680px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-eleven-content section ul {
  display: grid;
  gap: 8px;
  margin: 4px 0 0 !important;
  padding-left: 20px !important;
}

.vb-storage-eleven-content section li {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-eleven-status {
  padding: 13px 15px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 800;
}

.vb-storage-eleven-status strong {
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
}

@media (max-width: 640px) {
  .vb-storage-eleven-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-eleven-nav {
    flex-direction: column;
  }

  .vb-storage-eleven-nav button {
    width: 100%;
  }
}

This active tab localStorage example is useful for product detail tabs, pricing tabs, account settings, dashboard panels, documentation pages, FAQ categories, and custom WordPress content sections.

12. Product Comparison List Saved with LocalStorage

A product comparison list saved with localStorage is useful for ecommerce stores, affiliate product roundups, SaaS pricing pages, hosting comparison sites, software directories, and product review websites. It lets visitors add items to a comparison table and keep that selection after refresh.

This example lets users add and remove products from a comparison table. JavaScript stores selected product IDs in localStorage, updates the table instantly, prevents duplicate items, and restores the comparison list after page reload.

Example 12

Saved Product Compare

Add products to the comparison table. Refresh the page and the selected products stay saved in localStorage.

Starter

Starter Plan

Good for small sites and simple landing pages.

19€
Business

Business Plan

Better for company websites and growing traffic.

49€
Pro

Pro Plan

Best for ecommerce, automation, and advanced features.

99€
Compare Products 0 selected

No products selected for comparison.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twelve]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwelveCompare";
  const products = Array.from(wrapper.querySelectorAll("[data-product]"));
  const compareTable = wrapper.querySelector("[data-compare-table]");
  const compareCount = wrapper.querySelector("[data-compare-count]");
  const clearButton = wrapper.querySelector("[data-clear-compare]");

  let selectedIds = [];

  function saveCompare() {
    localStorage.setItem(storageKey, JSON.stringify(selectedIds));
  }

  function loadCompare() {
    const savedCompare = localStorage.getItem(storageKey);
    if (!savedCompare) return;

    try {
      const parsedCompare = JSON.parse(savedCompare);
      selectedIds = Array.isArray(parsedCompare) ? parsedCompare : [];
    } catch (error) {
      selectedIds = [];
      localStorage.removeItem(storageKey);
    }
  }

  function getProductData(product) {
    return {
      id: product.getAttribute("data-id"),
      name: product.getAttribute("data-name"),
      price: product.getAttribute("data-price"),
      speed: product.getAttribute("data-speed"),
      support: product.getAttribute("data-support")
    };
  }

  function renderCompare() {
    products.forEach(function (product) {
      const productId = product.getAttribute("data-id");
      const isSelected = selectedIds.includes(productId);

      product.classList.toggle("is-selected", isSelected);
      product.querySelector("[data-add-compare]").textContent = isSelected ? "Added" : "Add to Compare";
    });

    compareCount.textContent = selectedIds.length === 1 ? "1 selected" : selectedIds.length + " selected";

    if (selectedIds.length === 0) {
      compareTable.innerHTML = '<p class="vb-storage-twelve-empty">No products selected for comparison.</p>';
      return;
    }

    const selectedProducts = selectedIds
      .map(function (id) {
        return products.find(function (product) {
          return product.getAttribute("data-id") === id;
        });
      })
      .filter(Boolean)
      .map(getProductData);

    let tableHtml =
      '<table>' +
        '<thead>' +
          '<tr>' +
            '<th>Product</th>' +
            '<th>Price</th>' +
            '<th>Speed</th>' +
            '<th>Support</th>' +
            '<th></th>' +
          '</tr>' +
        '</thead>' +
        '<tbody>';

    selectedProducts.forEach(function (product) {
      tableHtml +=
        '<tr>' +
          '<td>' + product.name + '</td>' +
          '<td>' + product.price + '</td>' +
          '<td>' + product.speed + '</td>' +
          '<td>' + product.support + '</td>' +
          '<td><button type="button" data-remove-compare="' + product.id + '">×</button></td>' +
        '</tr>';
    });

    tableHtml += '</tbody></table>';
    compareTable.innerHTML = tableHtml;
  }

  function toggleCompare(productId) {
    if (selectedIds.includes(productId)) {
      selectedIds = selectedIds.filter(function (id) {
        return id !== productId;
      });
    } else {
      selectedIds.push(productId);
    }

    saveCompare();
    renderCompare();
  }

  products.forEach(function (product) {
    product.querySelector("[data-add-compare]").addEventListener("click", function () {
      toggleCompare(product.getAttribute("data-id"));
    });
  });

  compareTable.addEventListener("click", function (event) {
    const removeId = event.target.getAttribute("data-remove-compare");
    if (!removeId) return;

    selectedIds = selectedIds.filter(function (id) {
      return id !== removeId;
    });

    saveCompare();
    renderCompare();
  });

  clearButton.addEventListener("click", function () {
    selectedIds = [];
    saveCompare();
    renderCompare();
  });

  loadCompare();
  renderCompare();
})();

HTML

<div class="vb-storage-twelve-demo">
  <div class="vb-storage-twelve-compare" data-vb-storage-twelve>
    <div class="vb-storage-twelve-head">
      <span class="vb-storage-twelve-kicker">Example 12</span>
      <h3>Saved Product Compare</h3>
      <p>Add products to the comparison table. Refresh the page and the selected products stay saved in localStorage.</p>
    </div>

    <div class="vb-storage-twelve-grid">
      <div class="vb-storage-twelve-products">
        <article data-product data-id="starter" data-name="Starter Plan" data-price="19€" data-speed="Fast" data-support="Email">
          <span>Starter</span>
          <h4>Starter Plan</h4>
          <p>Good for small sites and simple landing pages.</p>
          <strong>19€</strong>
          <button type="button" data-add-compare>Add to Compare</button>
        </article>

        <article data-product data-id="business" data-name="Business Plan" data-price="49€" data-speed="Very Fast" data-support="Priority">
          <span>Business</span>
          <h4>Business Plan</h4>
          <p>Better for company websites and growing traffic.</p>
          <strong>49€</strong>
          <button type="button" data-add-compare>Add to Compare</button>
        </article>

        <article data-product data-id="pro" data-name="Pro Plan" data-price="99€" data-speed="Ultra Fast" data-support="Premium">
          <span>Pro</span>
          <h4>Pro Plan</h4>
          <p>Best for ecommerce, automation, and advanced features.</p>
          <strong>99€</strong>
          <button type="button" data-add-compare>Add to Compare</button>
        </article>
      </div>

      <div class="vb-storage-twelve-tablebox">
        <div class="vb-storage-twelve-table-head">
          <strong>Compare Products</strong>
          <span data-compare-count>0 selected</span>
        </div>

        <div class="vb-storage-twelve-table" data-compare-table>
          <p class="vb-storage-twelve-empty">No products selected for comparison.</p>
        </div>

        <button type="button" class="vb-storage-twelve-clear" data-clear-compare>Clear Compare</button>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-twelve-demo,
.vb-storage-twelve-demo * {
  box-sizing: border-box;
}

.vb-storage-twelve-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(37, 99, 235, 0.18), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(236, 72, 153, 0.14), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #fdf2f8 55%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-storage-twelve-compare {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-twelve-head {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-twelve-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twelve-head h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twelve-head p {
  max-width: 780px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twelve-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  gap: 20px;
  align-items: start;
}

.vb-storage-twelve-products {
  display: grid;
  gap: 14px;
}

.vb-storage-twelve-products article {
  display: grid;
  gap: 9px;
  padding: 18px;
  border-radius: 24px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-storage-twelve-products article.is-selected {
  border-color: rgba(37, 99, 235, 0.54);
  box-shadow: 0 16px 40px rgba(37, 99, 235, 0.10);
}

.vb-storage-twelve-products article span {
  display: inline-flex;
  justify-self: start;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-storage-twelve-products article h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 23px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
}

.vb-storage-twelve-products article p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-storage-twelve-products article strong {
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 26px;
  font-weight: 950;
}

.vb-storage-twelve-products article button,
.vb-storage-twelve-clear {
  min-height: 42px;
  border: 0;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twelve-products article button {
  justify-self: start;
  padding: 0 14px;
  background: linear-gradient(135deg, #2563eb, #db2777);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-twelve-products article.is-selected button {
  background: #e0e7ff;
  color: #3730a3 !important;
  -webkit-text-fill-color: #3730a3 !important;
}

.vb-storage-twelve-tablebox {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 28px;
  background: #111827;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.24);
}

.vb-storage-twelve-table-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.vb-storage-twelve-table-head strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  font-weight: 950;
}

.vb-storage-twelve-table-head span {
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-twelve-table {
  min-height: 270px;
  overflow-x: auto;
}

.vb-storage-twelve-empty {
  display: grid;
  place-items: center;
  min-height: 270px;
  margin: 0 !important;
  border: 1px dashed rgba(255,255,255,0.22);
  border-radius: 20px;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-twelve-table table {
  width: 100%;
  min-width: 520px;
  border-collapse: separate;
  border-spacing: 0 9px;
}

.vb-storage-twelve-table th {
  padding: 10px;
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  text-align: left;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-storage-twelve-table td {
  padding: 12px 10px;
  background: rgba(255,255,255,0.08);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 800;
}

.vb-storage-twelve-table td:first-child {
  border-radius: 14px 0 0 14px;
}

.vb-storage-twelve-table td:last-child {
  border-radius: 0 14px 14px 0;
}

.vb-storage-twelve-table td button {
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: 999px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 16px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twelve-clear {
  background: #ffffff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
}

@media (max-width: 960px) {
  .vb-storage-twelve-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-twelve-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This product comparison localStorage example is useful for ecommerce product tables, affiliate comparison posts, hosting comparison pages, SaaS pricing tables, software directories, product review websites, and WooCommerce-style catalog features.

Need this on your website?

Can’t build it yourself? We can create it for you.

If you like these JavaScript localStorage features but want a custom version for your website, landing page, ecommerce store, or WordPress project, contact us and tell us what you need.

Contact Us

13. Quiz Progress Saved with LocalStorage

Saving quiz progress with localStorage is useful for online quizzes, course lessons, onboarding tests, product recommendation quizzes, lead generation quizzes, and learning platforms. It helps users continue from the same question after refreshing or returning later.

This example saves the current quiz question, selected answers, and score state in localStorage. Visitors can answer questions, move forward, refresh the page, and continue from the saved quiz progress.

Example 13

Saved Quiz Progress

Answer the quiz, refresh the page, and localStorage restores your current question and selected answers.

Question 1 of 3 Score: 0

Loading question...

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-thirteen]");
  if (!wrapper) return;

  const storageKey = "vbStorageThirteenQuiz";
  const questionCount = wrapper.querySelector("[data-question-count]");
  const scoreLabel = wrapper.querySelector("[data-score-label]");
  const progressBar = wrapper.querySelector("[data-progress-bar]");
  const questionTitle = wrapper.querySelector("[data-question-title]");
  const optionsBox = wrapper.querySelector("[data-options]");
  const prevButton = wrapper.querySelector("[data-prev-question]");
  const nextButton = wrapper.querySelector("[data-next-question]");
  const resultBox = wrapper.querySelector("[data-result-box]");
  const resultTitle = wrapper.querySelector("[data-result-title]");
  const resultText = wrapper.querySelector("[data-result-text]");
  const resetButton = wrapper.querySelector("[data-reset-quiz]");

  const questions = [
    {
      title: "What does localStorage store values as?",
      options: ["Numbers only", "Strings", "CSS variables"],
      correct: "Strings"
    },
    {
      title: "Which method saves a value in localStorage?",
      options: ["localStorage.setItem()", "localStorage.push()", "localStorage.save()"],
      correct: "localStorage.setItem()"
    },
    {
      title: "What is commonly used to store arrays or objects?",
      options: ["JSON.stringify()", "document.write()", "parseInt()"],
      correct: "JSON.stringify()"
    }
  ];

  let state = {
    currentQuestion: 0,
    answers: {}
  };

  function saveState() {
    localStorage.setItem(storageKey, JSON.stringify(state));
  }

  function loadState() {
    const savedState = localStorage.getItem(storageKey);
    if (!savedState) return;

    try {
      const parsedState = JSON.parse(savedState);
      state.currentQuestion = Number(parsedState.currentQuestion) || 0;
      state.answers = parsedState.answers || {};
    } catch (error) {
      localStorage.removeItem(storageKey);
    }
  }

  function getScore() {
    return questions.reduce(function (score, question, index) {
      return state.answers[index] === question.correct ? score + 1 : score;
    }, 0);
  }

  function renderQuiz() {
    const question = questions[state.currentQuestion];
    const selectedAnswer = state.answers[state.currentQuestion];

    questionCount.textContent = "Question " + (state.currentQuestion + 1) + " of " + questions.length;
    scoreLabel.textContent = "Score: " + getScore();
    progressBar.style.width = ((state.currentQuestion + 1) / questions.length * 100) + "%";
    questionTitle.textContent = question.title;
    optionsBox.innerHTML = "";

    question.options.forEach(function (option) {
      const label = document.createElement("label");
      label.className = "vb-storage-thirteen-option";
      label.classList.toggle("is-selected", selectedAnswer === option);

      label.innerHTML =
        '<input type="radio" name="quizAnswer" value="' + option + '"' + (selectedAnswer === option ? " checked" : "") + '>' +
        '<span>' + option + '</span>';

      optionsBox.appendChild(label);
    });

    prevButton.disabled = state.currentQuestion === 0;
    nextButton.textContent = state.currentQuestion === questions.length - 1 ? "Finish Quiz" : "Next";
    resultBox.hidden = true;

    saveState();
  }

  optionsBox.addEventListener("change", function (event) {
    if (event.target.name !== "quizAnswer") return;

    state.answers[state.currentQuestion] = event.target.value;
    saveState();
    renderQuiz();
  });

  prevButton.addEventListener("click", function () {
    state.currentQuestion = Math.max(0, state.currentQuestion - 1);
    renderQuiz();
  });

  nextButton.addEventListener("click", function () {
    if (state.currentQuestion < questions.length - 1) {
      state.currentQuestion += 1;
      renderQuiz();
      return;
    }

    const score = getScore();
    resultBox.hidden = false;
    resultTitle.textContent = "Quiz complete";
    resultText.textContent = "You scored " + score + " out of " + questions.length + ". Your progress is still saved in localStorage.";
  });

  resetButton.addEventListener("click", function () {
    state = {
      currentQuestion: 0,
      answers: {}
    };

    localStorage.removeItem(storageKey);
    renderQuiz();
  });

  loadState();
  renderQuiz();
})();

HTML

<div class="vb-storage-thirteen-demo">
  <div class="vb-storage-thirteen-quiz" data-vb-storage-thirteen>
    <div class="vb-storage-thirteen-intro">
      <span class="vb-storage-thirteen-kicker">Example 13</span>
      <h3>Saved Quiz Progress</h3>
      <p>Answer the quiz, refresh the page, and localStorage restores your current question and selected answers.</p>
    </div>

    <div class="vb-storage-thirteen-card">
      <div class="vb-storage-thirteen-top">
        <span data-question-count>Question 1 of 3</span>
        <strong data-score-label>Score: 0</strong>
      </div>

      <div class="vb-storage-thirteen-progress">
        <span data-progress-bar></span>
      </div>

      <div class="vb-storage-thirteen-question">
        <h4 data-question-title>Loading question...</h4>
        <div class="vb-storage-thirteen-options" data-options></div>
      </div>

      <div class="vb-storage-thirteen-actions">
        <button type="button" data-prev-question>Back</button>
        <button type="button" data-next-question>Next</button>
      </div>

      <div class="vb-storage-thirteen-result" data-result-box hidden>
        <strong data-result-title>Quiz complete</strong>
        <p data-result-text>Your result will appear here.</p>
      </div>

      <button type="button" class="vb-storage-thirteen-reset" data-reset-quiz>Reset Saved Quiz</button>
    </div>
  </div>
</div>

CSS

.vb-storage-thirteen-demo,
.vb-storage-thirteen-demo * {
  box-sizing: border-box;
}

.vb-storage-thirteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 16%, rgba(99, 102, 241, 0.18), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(16, 185, 129, 0.16), transparent 34%),
    linear-gradient(135deg, #eef2ff 0%, #ecfdf5 54%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.18);
  box-shadow: 0 28px 80px rgba(67, 56, 202, 0.10);
}

.vb-storage-thirteen-quiz {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #111827;
  box-shadow: 0 28px 90px rgba(17, 24, 39, 0.28);
}

.vb-storage-thirteen-intro {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  padding: clamp(18px, 3vw, 26px);
}

.vb-storage-thirteen-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(99, 102, 241, 0.20);
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-thirteen-intro h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-thirteen-intro p {
  max-width: 540px;
  margin: 0 !important;
  color: #d1d5db !important;
  -webkit-text-fill-color: #d1d5db !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-thirteen-card {
  display: grid;
  gap: 16px;
  min-width: 0;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-storage-thirteen-top,
.vb-storage-thirteen-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-thirteen-top span,
.vb-storage-thirteen-top strong {
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-thirteen-top span {
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
}

.vb-storage-thirteen-top strong {
  color: #059669 !important;
  -webkit-text-fill-color: #059669 !important;
}

.vb-storage-thirteen-progress {
  height: 12px;
  overflow: hidden;
  border-radius: 999px;
  background: #e5e7eb;
}

.vb-storage-thirteen-progress span {
  display: block;
  width: 33.33%;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(135deg, #6366f1, #10b981);
  transition: width 0.25s ease;
}

.vb-storage-thirteen-question {
  display: grid;
  gap: 16px;
  min-height: 270px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 24px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-thirteen-question h4 {
  margin: 0 !important;
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(24px, 3vw, 34px) !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  letter-spacing: -0.04em;
}

.vb-storage-thirteen-options {
  display: grid;
  gap: 10px;
}

.vb-storage-thirteen-option {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  padding: 13px 14px;
  border: 1px solid rgba(148, 163, 184, 0.24);
  border-radius: 16px;
  background: #ffffff;
  cursor: pointer;
}

.vb-storage-thirteen-option.is-selected {
  border-color: rgba(99, 102, 241, 0.70);
  background: #eef2ff;
}

.vb-storage-thirteen-option input {
  width: 18px;
  height: 18px;
  accent-color: #6366f1;
}

.vb-storage-thirteen-option span {
  color: #1f2937 !important;
  -webkit-text-fill-color: #1f2937 !important;
  font-size: 15px;
  line-height: 1.35;
  font-weight: 750;
}

.vb-storage-thirteen-actions button,
.vb-storage-thirteen-reset {
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-thirteen-actions button {
  padding: 0 16px;
  background: #e0e7ff;
  color: #3730a3 !important;
  -webkit-text-fill-color: #3730a3 !important;
}

.vb-storage-thirteen-actions button:last-child {
  background: linear-gradient(135deg, #6366f1, #10b981);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-thirteen-actions button:disabled {
  opacity: 0.52;
  cursor: not-allowed;
}

.vb-storage-thirteen-result {
  padding: 16px;
  border-radius: 20px;
  background: #ecfdf5;
  border: 1px solid rgba(16, 185, 129, 0.24);
}

.vb-storage-thirteen-result strong {
  display: block;
  margin-bottom: 6px;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
  font-size: 18px;
  font-weight: 950;
}

.vb-storage-thirteen-result p {
  margin: 0 !important;
  color: #065f46 !important;
  -webkit-text-fill-color: #065f46 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 750;
}

.vb-storage-thirteen-reset {
  justify-self: start;
  padding: 0 14px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

@media (max-width: 920px) {
  .vb-storage-thirteen-quiz {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-thirteen-intro h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-thirteen-actions {
    flex-direction: column;
  }

  .vb-storage-thirteen-actions button,
  .vb-storage-thirteen-reset {
    width: 100%;
  }
}

This quiz progress localStorage example is useful for JavaScript quiz apps, online course lessons, onboarding questionnaires, product recommendation quizzes, lead generation quizzes, learning platforms, and interactive website assessments.

14. Calculator History Saved with LocalStorage

Saving calculator history with localStorage is useful for price calculators, loan calculators, quote calculators, discount calculators, BMI calculators, tax calculators, and form-based tools. It helps users compare previous results without needing a backend database.

This example calculates a simple project price estimate from hours and hourly rate. JavaScript saves each calculated result into localStorage, displays a history list, and restores previous calculations after page refresh.

Example 14

Saved Calculator History

Calculate a project estimate and save every result in localStorage for later comparison.

Current estimate 540€
Saved History

No saved calculations yet.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-fourteen]");
  if (!wrapper) return;

  const storageKey = "vbStorageFourteenCalculatorHistory";
  const form = wrapper.querySelector("[data-calc-form]");
  const projectInput = wrapper.querySelector("[data-project-name]");
  const hoursInput = wrapper.querySelector("[data-hours]");
  const rateInput = wrapper.querySelector("[data-rate]");
  const currentTotal = wrapper.querySelector("[data-current-total]");
  const historyList = wrapper.querySelector("[data-history-list]");
  const clearButton = wrapper.querySelector("[data-clear-history]");

  let history = [];

  function saveHistory() {
    localStorage.setItem(storageKey, JSON.stringify(history));
  }

  function loadHistory() {
    const savedHistory = localStorage.getItem(storageKey);
    if (!savedHistory) return;

    try {
      const parsedHistory = JSON.parse(savedHistory);
      history = Array.isArray(parsedHistory) ? parsedHistory : [];
    } catch (error) {
      history = [];
      localStorage.removeItem(storageKey);
    }
  }

  function calculateTotal() {
    const hours = Number(hoursInput.value) || 0;
    const rate = Number(rateInput.value) || 0;
    return hours * rate;
  }

  function updateCurrentTotal() {
    currentTotal.textContent = calculateTotal() + "€";
  }

  function renderHistory() {
    historyList.innerHTML = "";

    if (history.length === 0) {
      historyList.innerHTML = '<p class="vb-storage-fourteen-empty">No saved calculations yet.</p>';
      return;
    }

    history.forEach(function (item) {
      const historyItem = document.createElement("div");
      historyItem.className = "vb-storage-fourteen-item";

      historyItem.innerHTML =
        '<strong>' + item.project + '</strong>' +
        '<span>' + item.total + '€</span>' +
        '<small>' + item.hours + ' hours × ' + item.rate + '€/h · ' + item.date + '</small>';

      historyList.appendChild(historyItem);
    });
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();

    const total = calculateTotal();
    const project = projectInput.value.trim() || "Untitled estimate";

    history.unshift({
      id: String(Date.now()),
      project: project,
      hours: Number(hoursInput.value) || 0,
      rate: Number(rateInput.value) || 0,
      total: total,
      date: new Date().toLocaleDateString()
    });

    history = history.slice(0, 6);
    saveHistory();
    renderHistory();
    updateCurrentTotal();
  });

  hoursInput.addEventListener("input", updateCurrentTotal);
  rateInput.addEventListener("input", updateCurrentTotal);

  clearButton.addEventListener("click", function () {
    history = [];
    localStorage.removeItem(storageKey);
    renderHistory();
  });

  loadHistory();
  renderHistory();
  updateCurrentTotal();
})();

HTML

<div class="vb-storage-fourteen-demo">
  <div class="vb-storage-fourteen-app" data-vb-storage-fourteen>
    <div class="vb-storage-fourteen-calc">
      <span class="vb-storage-fourteen-kicker">Example 14</span>
      <h3>Saved Calculator History</h3>
      <p>Calculate a project estimate and save every result in localStorage for later comparison.</p>

      <form data-calc-form class="vb-storage-fourteen-form">
        <label>
          <span>Project name</span>
          <input type="text" data-project-name placeholder="Landing page build">
        </label>

        <div class="vb-storage-fourteen-fields">
          <label>
            <span>Hours</span>
            <input type="number" data-hours min="1" value="12">
          </label>

          <label>
            <span>Hourly rate (€)</span>
            <input type="number" data-rate min="1" value="45">
          </label>
        </div>

        <button type="submit">Calculate Estimate</button>
      </form>

      <div class="vb-storage-fourteen-total">
        <span>Current estimate</span>
        <strong data-current-total>540€</strong>
      </div>
    </div>

    <div class="vb-storage-fourteen-history">
      <div class="vb-storage-fourteen-history-head">
        <strong>Saved History</strong>
        <button type="button" data-clear-history>Clear</button>
      </div>

      <div class="vb-storage-fourteen-list" data-history-list>
        <p class="vb-storage-fourteen-empty">No saved calculations yet.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-fourteen-demo,
.vb-storage-fourteen-demo * {
  box-sizing: border-box;
}

.vb-storage-fourteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(249, 115, 22, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(37, 99, 235, 0.16), transparent 34%),
    linear-gradient(135deg, #fff7ed 0%, #eff6ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(249, 115, 22, 0.18);
  box-shadow: 0 28px 80px rgba(154, 52, 18, 0.10);
}

.vb-storage-fourteen-app {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-fourteen-calc,
.vb-storage-fourteen-history {
  min-width: 0;
}

.vb-storage-fourteen-calc {
  display: grid;
  gap: 16px;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #111827;
}

.vb-storage-fourteen-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(249, 115, 22, 0.16);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-fourteen-calc h3 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-fourteen-calc p {
  margin: 0 !important;
  color: #d1d5db !important;
  -webkit-text-fill-color: #d1d5db !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-fourteen-form {
  display: grid;
  gap: 12px;
}

.vb-storage-fourteen-form label {
  display: grid;
  gap: 7px;
}

.vb-storage-fourteen-form label span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-fourteen-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-storage-fourteen-form input {
  width: 100%;
  min-height: 50px;
  padding: 0 15px;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 16px;
  background: rgba(255,255,255,0.09);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-storage-fourteen-form input::placeholder {
  color: #9ca3af;
  -webkit-text-fill-color: #9ca3af;
}

.vb-storage-fourteen-form input:focus {
  border-color: rgba(249, 115, 22, 0.70);
  box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.12);
}

.vb-storage-fourteen-form button {
  min-height: 50px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #f97316, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.22);
}

.vb-storage-fourteen-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px;
  border-radius: 22px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-fourteen-total span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-fourteen-total strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 34px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-fourteen-history {
  display: grid;
  gap: 14px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-fourteen-history-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-fourteen-history-head strong {
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-fourteen-history-head button {
  min-height: 38px;
  padding: 0 13px;
  border: 0;
  border-radius: 999px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-fourteen-list {
  display: grid;
  gap: 10px;
  min-height: 340px;
}

.vb-storage-fourteen-empty {
  display: grid;
  place-items: center;
  min-height: 340px;
  margin: 0 !important;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  border-radius: 22px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-fourteen-item {
  display: grid;
  gap: 7px;
  padding: 14px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.06);
}

.vb-storage-fourteen-item strong {
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 16px;
  line-height: 1.2;
  font-weight: 950;
}

.vb-storage-fourteen-item span {
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
  font-size: 22px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-fourteen-item small {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 750;
}

@media (max-width: 920px) {
  .vb-storage-fourteen-app {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-fourteen-fields {
    grid-template-columns: 1fr;
  }

  .vb-storage-fourteen-calc h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-fourteen-total {
    align-items: flex-start;
    flex-direction: column;
  }
}

This calculator history localStorage example is useful for price calculators, quote estimators, loan calculators, discount calculators, project calculators, service estimate forms, and browser-based calculation tools.

15. Continue Reading Progress Saved with LocalStorage

Saving reading progress with localStorage is useful for blogs, documentation pages, tutorials, online courses, long-form guides, knowledge bases, and article websites. It helps readers continue from where they left off after returning to a page.

This example simulates a long article area with section buttons. JavaScript saves the last selected article section in localStorage, updates the reading progress indicator, and restores the same section after refresh.

Example 15

Continue Reading Progress

Select article sections and refresh the page. localStorage restores the last section you were reading.

Section 01

Introduction

This section introduces the topic and explains why saving reading progress can improve long-form content experiences.

Section 02

Setup

This section explains how a page can use JavaScript, section buttons, and localStorage to remember the reader’s last position.

Section 03

Examples

This section could contain code examples, screenshots, tutorial steps, videos, or detailed learning material.

Section 04

Summary

This section closes the guide and lets the reader return later without losing where they stopped.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-fifteen]");
  if (!wrapper) return;

  const storageKey = "vbStorageFifteenReadingSection";
  const buttons = Array.from(wrapper.querySelectorAll("[data-section-button]"));
  const panels = Array.from(wrapper.querySelectorAll("[data-section-panel]"));
  const progressBar = wrapper.querySelector("[data-reading-progress]");
  const progressLabel = wrapper.querySelector("[data-progress-label]");
  const resetButton = wrapper.querySelector("[data-reset-reading]");

  function activateSection(sectionKey) {
    const activeIndex = buttons.findIndex(function (button) {
      return button.getAttribute("data-section-button") === sectionKey;
    });

    const safeIndex = activeIndex >= 0 ? activeIndex : 0;
    const safeKey = buttons[safeIndex].getAttribute("data-section-button");
    const percent = Math.round(((safeIndex + 1) / buttons.length) * 100);

    buttons.forEach(function (button) {
      button.classList.toggle("is-active", button.getAttribute("data-section-button") === safeKey);
    });

    panels.forEach(function (panel) {
      panel.classList.toggle("is-active", panel.getAttribute("data-section-panel") === safeKey);
    });

    progressBar.style.width = percent + "%";
    progressLabel.textContent = percent + "% complete";
    localStorage.setItem(storageKey, safeKey);
  }

  buttons.forEach(function (button) {
    button.addEventListener("click", function () {
      activateSection(button.getAttribute("data-section-button"));
    });
  });

  resetButton.addEventListener("click", function () {
    localStorage.removeItem(storageKey);
    activateSection("intro");
  });

  activateSection(localStorage.getItem(storageKey) || "intro");
})();

HTML

<div class="vb-storage-fifteen-demo">
  <div class="vb-storage-fifteen-reader" data-vb-storage-fifteen>
    <div class="vb-storage-fifteen-head">
      <span class="vb-storage-fifteen-kicker">Example 15</span>
      <h3>Continue Reading Progress</h3>
      <p>Select article sections and refresh the page. localStorage restores the last section you were reading.</p>
    </div>

    <div class="vb-storage-fifteen-layout">
      <aside class="vb-storage-fifteen-sidebar">
        <strong>Article Sections</strong>
        <button type="button" data-section-button="intro" class="is-active">1. Introduction</button>
        <button type="button" data-section-button="setup">2. Setup</button>
        <button type="button" data-section-button="examples">3. Examples</button>
        <button type="button" data-section-button="summary">4. Summary</button>

        <div class="vb-storage-fifteen-progress">
          <span>Reading progress</span>
          <div><i data-reading-progress></i></div>
          <small data-progress-label>25% complete</small>
        </div>
      </aside>

      <article class="vb-storage-fifteen-content">
        <section data-section-panel="intro" class="is-active">
          <span>Section 01</span>
          <h4>Introduction</h4>
          <p>This section introduces the topic and explains why saving reading progress can improve long-form content experiences.</p>
        </section>

        <section data-section-panel="setup">
          <span>Section 02</span>
          <h4>Setup</h4>
          <p>This section explains how a page can use JavaScript, section buttons, and localStorage to remember the reader’s last position.</p>
        </section>

        <section data-section-panel="examples">
          <span>Section 03</span>
          <h4>Examples</h4>
          <p>This section could contain code examples, screenshots, tutorial steps, videos, or detailed learning material.</p>
        </section>

        <section data-section-panel="summary">
          <span>Section 04</span>
          <h4>Summary</h4>
          <p>This section closes the guide and lets the reader return later without losing where they stopped.</p>
        </section>
      </article>
    </div>

    <button type="button" class="vb-storage-fifteen-reset" data-reset-reading>Reset Reading Progress</button>
  </div>
</div>

CSS

.vb-storage-fifteen-demo,
.vb-storage-fifteen-demo * {
  box-sizing: border-box;
}

.vb-storage-fifteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(6, 182, 212, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(124, 58, 237, 0.14), transparent 34%),
    linear-gradient(135deg, #ecfeff 0%, #f5f3ff 55%, #ffffff 100%) !important;
  border: 1px solid rgba(6, 182, 212, 0.18);
  box-shadow: 0 28px 80px rgba(14, 116, 144, 0.10);
}

.vb-storage-fifteen-reader {
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-fifteen-head {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-fifteen-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #cffafe;
  color: #0e7490 !important;
  -webkit-text-fill-color: #0e7490 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-fifteen-head h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-fifteen-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-fifteen-layout {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: 18px;
  align-items: stretch;
}

.vb-storage-fifteen-sidebar {
  display: grid;
  align-content: start;
  gap: 10px;
  padding: 18px;
  border-radius: 28px;
  background: #0f172a;
}

.vb-storage-fifteen-sidebar strong {
  margin-bottom: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  line-height: 1.15;
  font-weight: 950;
}

.vb-storage-fifteen-sidebar button {
  min-height: 44px;
  padding: 10px 13px;
  border: 0;
  border-radius: 16px;
  background: rgba(255,255,255,0.08);
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 13px;
  font-weight: 850;
  text-align: left;
  cursor: pointer;
}

.vb-storage-fifteen-sidebar button.is-active {
  background: linear-gradient(135deg, #06b6d4, #7c3aed);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-fifteen-progress {
  display: grid;
  gap: 8px;
  margin-top: 12px;
  padding: 14px;
  border-radius: 20px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.10);
}

.vb-storage-fifteen-progress span,
.vb-storage-fifteen-progress small {
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-storage-fifteen-progress div {
  height: 10px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
}

.vb-storage-fifteen-progress i {
  display: block;
  width: 25%;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(135deg, #06b6d4, #a78bfa);
  transition: width 0.25s ease;
}

.vb-storage-fifteen-content {
  min-height: 420px;
  border-radius: 28px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
  overflow: hidden;
}

.vb-storage-fifteen-content section {
  display: none;
  min-height: 420px;
  padding: clamp(24px, 5vw, 54px);
  background:
    radial-gradient(circle at 90% 12%, rgba(6, 182, 212, 0.16), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
}

.vb-storage-fifteen-content section.is-active {
  display: grid;
  align-content: center;
  gap: 14px;
}

.vb-storage-fifteen-content section span {
  display: inline-flex;
  justify-self: start;
  padding: 8px 11px;
  border-radius: 999px;
  background: #cffafe;
  color: #0e7490 !important;
  -webkit-text-fill-color: #0e7490 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-storage-fifteen-content section h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-storage-fifteen-content section p {
  max-width: 640px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 17px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-storage-fifteen-reset {
  min-height: 42px;
  margin-top: 16px;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

@media (max-width: 860px) {
  .vb-storage-fifteen-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-fifteen-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-fifteen-reset {
    width: 100%;
  }
}

This continue reading localStorage example is useful for long blog posts, documentation pages, online lessons, tutorials, course platforms, knowledge bases, and step-by-step educational content.

16. Accordion Open State Saved with LocalStorage

Saving accordion open state with localStorage is useful for FAQ sections, product detail panels, support centers, documentation pages, settings panels, and course lessons. When a visitor opens a panel and refreshes the page, the same accordion item can stay open.

This example saves the currently open accordion item in localStorage. JavaScript updates the active panel when a button is clicked, stores the selected panel key, and restores the same open panel after refresh.

Example 16

Saved Accordion State

Open an FAQ item and refresh the page. localStorage restores the same open accordion panel.

Shipping options can be displayed here. This panel stays open after refresh because the active accordion key is saved in localStorage.

You can use this panel for payment details, invoice options, card payments, bank links, or custom ecommerce checkout information.

This panel can include support information, response times, help center links, documentation, or contact details.

Yes. The same localStorage logic can be used for product accordions, settings panels, documentation sections, and FAQ blocks.

Saved open panel: shipping

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-sixteen]");
  if (!wrapper) return;

  const storageKey = "vbStorageSixteenAccordion";
  const items = wrapper.querySelectorAll("[data-accordion-item]");
  const buttons = wrapper.querySelectorAll("[data-accordion-button]");
  const savedPanelLabel = wrapper.querySelector("[data-saved-panel]");
  const resetButton = wrapper.querySelector("[data-reset-accordion]");

  function openPanel(panelKey) {
    items.forEach(function (item) {
      item.classList.toggle("is-active", item.getAttribute("data-accordion-item") === panelKey);
    });

    savedPanelLabel.textContent = panelKey;
    localStorage.setItem(storageKey, panelKey);
  }

  buttons.forEach(function (button) {
    button.addEventListener("click", function () {
      openPanel(button.getAttribute("data-accordion-button"));
    });
  });

  resetButton.addEventListener("click", function () {
    localStorage.removeItem(storageKey);
    openPanel("shipping");
  });

  openPanel(localStorage.getItem(storageKey) || "shipping");
})();

HTML

<div class="vb-storage-sixteen-demo">
  <div class="vb-storage-sixteen-faq" data-vb-storage-sixteen>
    <div class="vb-storage-sixteen-head">
      <span class="vb-storage-sixteen-kicker">Example 16</span>
      <h3>Saved Accordion State</h3>
      <p>Open an FAQ item and refresh the page. localStorage restores the same open accordion panel.</p>
    </div>

    <div class="vb-storage-sixteen-list">
      <div class="vb-storage-sixteen-item is-active" data-accordion-item="shipping">
        <button type="button" data-accordion-button="shipping">
          <span>How does shipping work?</span>
          <strong>+</strong>
        </button>
        <div class="vb-storage-sixteen-panel">
          <p>Shipping options can be displayed here. This panel stays open after refresh because the active accordion key is saved in localStorage.</p>
        </div>
      </div>

      <div class="vb-storage-sixteen-item" data-accordion-item="payment">
        <button type="button" data-accordion-button="payment">
          <span>Which payment methods are available?</span>
          <strong>+</strong>
        </button>
        <div class="vb-storage-sixteen-panel">
          <p>You can use this panel for payment details, invoice options, card payments, bank links, or custom ecommerce checkout information.</p>
        </div>
      </div>

      <div class="vb-storage-sixteen-item" data-accordion-item="support">
        <button type="button" data-accordion-button="support">
          <span>Do you offer support?</span>
          <strong>+</strong>
        </button>
        <div class="vb-storage-sixteen-panel">
          <p>This panel can include support information, response times, help center links, documentation, or contact details.</p>
        </div>
      </div>

      <div class="vb-storage-sixteen-item" data-accordion-item="custom">
        <button type="button" data-accordion-button="custom">
          <span>Can this be customized?</span>
          <strong>+</strong>
        </button>
        <div class="vb-storage-sixteen-panel">
          <p>Yes. The same localStorage logic can be used for product accordions, settings panels, documentation sections, and FAQ blocks.</p>
        </div>
      </div>
    </div>

    <div class="vb-storage-sixteen-status">
      Saved open panel: <strong data-saved-panel>shipping</strong>
      <button type="button" data-reset-accordion>Reset</button>
    </div>
  </div>
</div>

CSS

.vb-storage-sixteen-demo,
.vb-storage-sixteen-demo * {
  box-sizing: border-box;
}

.vb-storage-sixteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(59, 130, 246, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(16, 185, 129, 0.15), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfdf5 56%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.18);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-storage-sixteen-faq {
  max-width: 960px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-sixteen-head {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-sixteen-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-sixteen-head h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-sixteen-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-sixteen-list {
  display: grid;
  gap: 12px;
}

.vb-storage-sixteen-item {
  overflow: hidden;
  border: 1px solid rgba(148, 163, 184, 0.24);
  border-radius: 22px;
  background: #f8fafc;
  transition: border-color 0.22s ease, box-shadow 0.22s ease;
}

.vb-storage-sixteen-item.is-active {
  border-color: rgba(37, 99, 235, 0.55);
  box-shadow: 0 16px 40px rgba(37, 99, 235, 0.10);
}

.vb-storage-sixteen-item button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  width: 100%;
  min-height: 64px;
  padding: 18px;
  border: 0;
  background: transparent;
  cursor: pointer;
  text-align: left;
}

.vb-storage-sixteen-item button span {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px;
  line-height: 1.25;
  font-weight: 950;
}

.vb-storage-sixteen-item button strong {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 20px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-sixteen-item.is-active button strong {
  background: linear-gradient(135deg, #2563eb, #10b981);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  transform: rotate(45deg);
}

.vb-storage-sixteen-panel {
  display: none;
  padding: 0 18px 18px;
}

.vb-storage-sixteen-item.is-active .vb-storage-sixteen-panel {
  display: block;
}

.vb-storage-sixteen-panel p {
  margin: 0 !important;
  padding: 16px;
  border-radius: 18px;
  background: #ffffff;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-sixteen-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 16px;
  padding: 14px 16px;
  border-radius: 20px;
  background: #eff6ff;
  color: #1e3a8a !important;
  -webkit-text-fill-color: #1e3a8a !important;
  font-size: 14px;
  font-weight: 850;
}

.vb-storage-sixteen-status strong {
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
}

.vb-storage-sixteen-status button {
  min-height: 38px;
  padding: 0 13px;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

@media (max-width: 560px) {
  .vb-storage-sixteen-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-sixteen-status button {
    width: 100%;
  }
}

This saved accordion state localStorage example is useful for FAQ sections, product information panels, checkout help blocks, documentation pages, support center categories, and dashboard settings panels.

17. User Preferences Saved with LocalStorage

Saving user preferences with localStorage is useful for dashboards, admin panels, SaaS apps, documentation websites, blog layouts, accessibility widgets, and custom website settings. It lets visitors keep interface choices such as font size, layout density, and accent style.

This example saves three interface preferences: text size, layout density, and accent color. JavaScript stores the settings object in localStorage, applies the choices instantly, and restores the same preferences after refresh.

Dashboard Preview medium · comfortable · blue

Saved Interface Settings

This preview changes based on your saved localStorage preferences. It can be used for dashboard settings, accessibility controls, user accounts, or blog reading options.

Preference Text size
Preference Density
Preference Accent

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-seventeen]");
  if (!wrapper) return;

  const storageKey = "vbStorageSeventeenPreferences";
  const selects = wrapper.querySelectorAll("[data-preference]");
  const resetButton = wrapper.querySelector("[data-reset-preferences]");
  const summary = wrapper.querySelector("[data-preference-summary]");

  const defaultPreferences = {
    size: "medium",
    density: "comfortable",
    accent: "blue"
  };

  let preferences = Object.assign({}, defaultPreferences);

  function savePreferences() {
    localStorage.setItem(storageKey, JSON.stringify(preferences));
  }

  function loadPreferences() {
    const savedPreferences = localStorage.getItem(storageKey);
    if (!savedPreferences) return;

    try {
      const parsedPreferences = JSON.parse(savedPreferences);
      preferences = Object.assign({}, defaultPreferences, parsedPreferences);
    } catch (error) {
      preferences = Object.assign({}, defaultPreferences);
      localStorage.removeItem(storageKey);
    }
  }

  function applyPreferences() {
    wrapper.setAttribute("data-size", preferences.size);
    wrapper.setAttribute("data-density", preferences.density);
    wrapper.setAttribute("data-accent", preferences.accent);

    selects.forEach(function (select) {
      const key = select.getAttribute("data-preference");
      select.value = preferences[key];
    });

    summary.textContent = preferences.size + " · " + preferences.density + " · " + preferences.accent;
    savePreferences();
  }

  selects.forEach(function (select) {
    select.addEventListener("change", function () {
      const key = select.getAttribute("data-preference");
      preferences[key] = select.value;
      applyPreferences();
    });
  });

  resetButton.addEventListener("click", function () {
    preferences = Object.assign({}, defaultPreferences);
    localStorage.removeItem(storageKey);
    applyPreferences();
  });

  loadPreferences();
  applyPreferences();
})();

HTML

<div class="vb-storage-seventeen-demo">
  <div class="vb-storage-seventeen-app" data-vb-storage-seventeen data-size="medium" data-density="comfortable" data-accent="blue">
    <aside class="vb-storage-seventeen-settings">
      <span class="vb-storage-seventeen-kicker">Example 17</span>
      <h3>Saved User Preferences</h3>
      <p>Change the settings and refresh the page. localStorage restores your preferred interface style.</p>

      <label>
        <span>Text size</span>
        <select data-preference="size">
          <option value="small">Small</option>
          <option value="medium">Medium</option>
          <option value="large">Large</option>
        </select>
      </label>

      <label>
        <span>Layout density</span>
        <select data-preference="density">
          <option value="compact">Compact</option>
          <option value="comfortable">Comfortable</option>
          <option value="spacious">Spacious</option>
        </select>
      </label>

      <label>
        <span>Accent color</span>
        <select data-preference="accent">
          <option value="blue">Blue</option>
          <option value="green">Green</option>
          <option value="purple">Purple</option>
        </select>
      </label>

      <button type="button" data-reset-preferences>Reset Preferences</button>
    </aside>

    <section class="vb-storage-seventeen-preview">
      <div class="vb-storage-seventeen-preview-top">
        <span>Dashboard Preview</span>
        <strong data-preference-summary>medium · comfortable · blue</strong>
      </div>

      <div class="vb-storage-seventeen-card">
        <h4>Saved Interface Settings</h4>
        <p>This preview changes based on your saved localStorage preferences. It can be used for dashboard settings, accessibility controls, user accounts, or blog reading options.</p>
      </div>

      <div class="vb-storage-seventeen-grid">
        <div>
          <span>Preference</span>
          <strong>Text size</strong>
        </div>
        <div>
          <span>Preference</span>
          <strong>Density</strong>
        </div>
        <div>
          <span>Preference</span>
          <strong>Accent</strong>
        </div>
      </div>
    </section>
  </div>
</div>

CSS

.vb-storage-seventeen-demo,
.vb-storage-seventeen-demo * {
  box-sizing: border-box;
}

.vb-storage-seventeen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(37, 99, 235, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(168, 85, 247, 0.14), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #faf5ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-storage-seventeen-app {
  display: grid;
  grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-seventeen-settings,
.vb-storage-seventeen-preview {
  min-width: 0;
}

.vb-storage-seventeen-settings {
  display: grid;
  align-content: start;
  gap: 13px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 28px;
  background: #0f172a;
}

.vb-storage-seventeen-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(96, 165, 250, 0.18);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-seventeen-settings h3 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 58px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-storage-seventeen-settings p {
  margin: 0 0 8px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-storage-seventeen-settings label {
  display: grid;
  gap: 7px;
}

.vb-storage-seventeen-settings label span {
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-seventeen-settings select {
  width: 100%;
  min-height: 46px;
  padding: 0 13px;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 15px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 750;
  outline: none;
}

.vb-storage-seventeen-settings button {
  min-height: 44px;
  margin-top: 4px;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-seventeen-preview {
  display: grid;
  gap: 16px;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-seventeen-preview-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.vb-storage-seventeen-preview-top span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-storage-seventeen-preview-top strong {
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-seventeen-card {
  padding: clamp(20px, 4vw, 34px);
  border-radius: 26px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 16px 42px rgba(15, 23, 42, 0.08);
}

.vb-storage-seventeen-card h4 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 34px !important;
  line-height: 1.05 !important;
  font-weight: 950 !important;
  letter-spacing: -0.055em;
}

.vb-storage-seventeen-card p {
  max-width: 650px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-seventeen-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-storage-seventeen-grid div {
  padding: 16px;
  border-radius: 20px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-seventeen-grid span {
  display: block;
  margin-bottom: 7px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-storage-seventeen-grid strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px;
  font-weight: 950;
}

.vb-storage-seventeen-app[data-size="small"] .vb-storage-seventeen-card p {
  font-size: 14px;
}

.vb-storage-seventeen-app[data-size="medium"] .vb-storage-seventeen-card p {
  font-size: 16px;
}

.vb-storage-seventeen-app[data-size="large"] .vb-storage-seventeen-card p {
  font-size: 19px;
}

.vb-storage-seventeen-app[data-density="compact"] .vb-storage-seventeen-card,
.vb-storage-seventeen-app[data-density="compact"] .vb-storage-seventeen-grid div {
  padding: 14px;
}

.vb-storage-seventeen-app[data-density="comfortable"] .vb-storage-seventeen-card {
  padding: 28px;
}

.vb-storage-seventeen-app[data-density="spacious"] .vb-storage-seventeen-card,
.vb-storage-seventeen-app[data-density="spacious"] .vb-storage-seventeen-grid div {
  padding: 34px;
}

.vb-storage-seventeen-app[data-accent="blue"] .vb-storage-seventeen-preview-top strong,
.vb-storage-seventeen-app[data-accent="blue"] .vb-storage-seventeen-grid strong {
  color: #2563eb !important;
  -webkit-text-fill-color: #2563eb !important;
}

.vb-storage-seventeen-app[data-accent="green"] .vb-storage-seventeen-preview-top strong,
.vb-storage-seventeen-app[data-accent="green"] .vb-storage-seventeen-grid strong {
  color: #059669 !important;
  -webkit-text-fill-color: #059669 !important;
}

.vb-storage-seventeen-app[data-accent="purple"] .vb-storage-seventeen-preview-top strong,
.vb-storage-seventeen-app[data-accent="purple"] .vb-storage-seventeen-grid strong {
  color: #7c3aed !important;
  -webkit-text-fill-color: #7c3aed !important;
}

@media (max-width: 920px) {
  .vb-storage-seventeen-app {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-seventeen-grid {
    grid-template-columns: 1fr;
  }

  .vb-storage-seventeen-settings h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This user preferences localStorage example is useful for dashboards, admin panels, user account settings, accessibility widgets, SaaS interfaces, documentation pages, and blog reading preference controls.

18. Discount Code Saved with LocalStorage

Saving a discount code with localStorage is useful for ecommerce landing pages, product checkout demos, pricing sections, cart pages, coupon banners, and sales campaigns. It lets a visitor apply a coupon and keep that discount visible after refreshing the page.

This example applies a discount code to a simple order summary. JavaScript saves the applied coupon in localStorage, recalculates the total, shows discount feedback, and restores the coupon after refresh.

Example 18

Saved Discount Code

Apply the code SAVE20, refresh the page, and localStorage keeps the discount active.

Coupon status: no code applied.
Order Summary No coupon
Website template 120€
Setup service 80€
Discount 0€
Total 200€

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-eighteen]");
  if (!wrapper) return;

  const storageKey = "vbStorageEighteenCoupon";
  const form = wrapper.querySelector("[data-coupon-form]");
  const input = wrapper.querySelector("[data-coupon-input]");
  const message = wrapper.querySelector("[data-coupon-message]");
  const badge = wrapper.querySelector("[data-coupon-badge]");
  const discountValue = wrapper.querySelector("[data-discount-value]");
  const orderTotal = wrapper.querySelector("[data-order-total]");
  const removeButton = wrapper.querySelector("[data-remove-coupon]");

  const baseTotal = 200;
  const validCoupon = "SAVE20";
  const discountPercent = 20;

  function applyCoupon(code) {
    const normalizedCode = (code || "").trim().toUpperCase();
    const isValid = normalizedCode === validCoupon;
    const discount = isValid ? Math.round(baseTotal * discountPercent / 100) : 0;
    const total = baseTotal - discount;

    input.value = normalizedCode;
    discountValue.textContent = "-" + discount + "€";
    orderTotal.textContent = total + "€";

    if (isValid) {
      localStorage.setItem(storageKey, normalizedCode);
      message.textContent = "Coupon status: SAVE20 applied and saved in localStorage.";
      badge.textContent = "SAVE20 active";
      badge.classList.add("is-active");
    } else {
      localStorage.removeItem(storageKey);
      message.textContent = normalizedCode ? "Coupon status: invalid code." : "Coupon status: no code applied.";
      badge.textContent = "No coupon";
      badge.classList.remove("is-active");
    }
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();
    applyCoupon(input.value);
  });

  removeButton.addEventListener("click", function () {
    localStorage.removeItem(storageKey);
    input.value = "";
    applyCoupon("");
  });

  applyCoupon(localStorage.getItem(storageKey) || "");
})();

HTML

<div class="vb-storage-eighteen-demo">
  <div class="vb-storage-eighteen-checkout" data-vb-storage-eighteen>
    <div class="vb-storage-eighteen-info">
      <span class="vb-storage-eighteen-kicker">Example 18</span>
      <h3>Saved Discount Code</h3>
      <p>Apply the code <strong>SAVE20</strong>, refresh the page, and localStorage keeps the discount active.</p>

      <form class="vb-storage-eighteen-form" data-coupon-form>
        <input type="text" data-coupon-input placeholder="Enter coupon code">
        <button type="submit">Apply Code</button>
      </form>

      <div class="vb-storage-eighteen-message" data-coupon-message>
        Coupon status: no code applied.
      </div>
    </div>

    <div class="vb-storage-eighteen-summary">
      <div class="vb-storage-eighteen-summary-head">
        <strong>Order Summary</strong>
        <span data-coupon-badge>No coupon</span>
      </div>

      <div class="vb-storage-eighteen-line">
        <span>Website template</span>
        <strong>120€</strong>
      </div>

      <div class="vb-storage-eighteen-line">
        <span>Setup service</span>
        <strong>80€</strong>
      </div>

      <div class="vb-storage-eighteen-line vb-storage-eighteen-discount">
        <span>Discount</span>
        <strong data-discount-value>0€</strong>
      </div>

      <div class="vb-storage-eighteen-total">
        <span>Total</span>
        <strong data-order-total>200€</strong>
      </div>

      <button type="button" data-remove-coupon>Remove Saved Coupon</button>
    </div>
  </div>
</div>

CSS

.vb-storage-eighteen-demo,
.vb-storage-eighteen-demo * {
  box-sizing: border-box;
}

.vb-storage-eighteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(16, 185, 129, 0.18), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(245, 158, 11, 0.16), transparent 34%),
    linear-gradient(135deg, #ecfdf5 0%, #fffbeb 56%, #ffffff 100%) !important;
  border: 1px solid rgba(16, 185, 129, 0.18);
  box-shadow: 0 28px 80px rgba(6, 95, 70, 0.10);
}

.vb-storage-eighteen-checkout {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(320px, 420px);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-eighteen-info,
.vb-storage-eighteen-summary {
  min-width: 0;
}

.vb-storage-eighteen-info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #0f172a;
}

.vb-storage-eighteen-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(16, 185, 129, 0.16);
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-eighteen-info h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-eighteen-info p {
  max-width: 560px;
  margin: 0 0 22px !important;
  color: #d1d5db !important;
  -webkit-text-fill-color: #d1d5db !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-eighteen-info p strong {
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
}

.vb-storage-eighteen-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  margin-bottom: 14px;
}

.vb-storage-eighteen-form input {
  min-width: 0;
  min-height: 50px;
  padding: 0 15px;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 750;
  outline: none;
}

.vb-storage-eighteen-form input::placeholder {
  color: #9ca3af;
  -webkit-text-fill-color: #9ca3af;
}

.vb-storage-eighteen-form input:focus {
  border-color: rgba(16, 185, 129, 0.70);
  box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.12);
}

.vb-storage-eighteen-form button {
  min-height: 50px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #10b981, #f59e0b);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-eighteen-message {
  padding: 13px 15px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  color: #d1fae5 !important;
  -webkit-text-fill-color: #d1fae5 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
}

.vb-storage-eighteen-summary {
  display: grid;
  gap: 14px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-eighteen-summary-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-eighteen-summary-head strong {
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-eighteen-summary-head span {
  padding: 7px 10px;
  border-radius: 999px;
  background: #e5e7eb;
  color: #374151 !important;
  -webkit-text-fill-color: #374151 !important;
  font-size: 12px;
  font-weight: 950;
}

.vb-storage-eighteen-summary-head span.is-active {
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
}

.vb-storage-eighteen-line,
.vb-storage-eighteen-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px;
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.18);
}

.vb-storage-eighteen-line span,
.vb-storage-eighteen-line strong {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 14px;
  font-weight: 850;
}

.vb-storage-eighteen-discount span,
.vb-storage-eighteen-discount strong {
  color: #059669 !important;
  -webkit-text-fill-color: #059669 !important;
}

.vb-storage-eighteen-total {
  background: #0f172a;
}

.vb-storage-eighteen-total span {
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-storage-eighteen-total strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 34px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-eighteen-summary > button {
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

@media (max-width: 900px) {
  .vb-storage-eighteen-checkout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-eighteen-form {
    grid-template-columns: 1fr;
  }

  .vb-storage-eighteen-info h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This discount code localStorage example is useful for ecommerce checkout pages, coupon banners, landing page offers, pricing tables, cart demos, product sales pages, and WooCommerce-style promotional features.

Need this on your website?

Can’t build it yourself? We can create it for you.

If you like these JavaScript localStorage features but want a custom version for your website, landing page, ecommerce store, or WordPress project, contact us and tell us what you need.

Contact Us

19. Remember Me Login Form with LocalStorage

A remember me login form with localStorage is a very common JavaScript example for login pages, account forms, dashboard prototypes, and membership websites. In real projects, passwords should never be stored in localStorage, but saving a harmless email or username preference is a practical UI pattern.

This example saves only the email address when the “Remember me” checkbox is enabled. JavaScript restores the email after refresh, updates the saved status, and clears the stored email when the checkbox is turned off.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-nineteen]");
  if (!wrapper) return;

  const storageKey = "vbStorageNineteenRememberEmail";
  const form = wrapper.querySelector("[data-login-form]");
  const emailInput = wrapper.querySelector("[data-login-email]");
  const passwordInput = wrapper.querySelector("[data-login-password]");
  const rememberCheckbox = wrapper.querySelector("[data-remember-email]");
  const status = wrapper.querySelector("[data-login-status]");
  const clearButton = wrapper.querySelector("[data-clear-login]");

  function updateStatus() {
    const savedEmail = localStorage.getItem(storageKey);

    if (savedEmail) {
      status.textContent = "Saved email: " + savedEmail;
      emailInput.value = savedEmail;
      rememberCheckbox.checked = true;
    } else {
      status.textContent = "Email not saved yet.";
      rememberCheckbox.checked = false;
    }
  }

  function saveEmailIfAllowed() {
    const email = emailInput.value.trim();

    if (rememberCheckbox.checked && email) {
      localStorage.setItem(storageKey, email);
    }

    if (!rememberCheckbox.checked) {
      localStorage.removeItem(storageKey);
    }

    updateStatus();
  }

  rememberCheckbox.addEventListener("change", saveEmailIfAllowed);
  emailInput.addEventListener("input", function () {
    if (rememberCheckbox.checked) {
      saveEmailIfAllowed();
    }
  });

  form.addEventListener("submit", function (event) {
    event.preventDefault();
    saveEmailIfAllowed();
    passwordInput.value = "";
    status.textContent = rememberCheckbox.checked
      ? "Demo login complete. Email preference is saved."
      : "Demo login complete. Email was not saved.";
  });

  clearButton.addEventListener("click", function () {
    localStorage.removeItem(storageKey);
    emailInput.value = "";
    passwordInput.value = "";
    updateStatus();
  });

  updateStatus();
})();

HTML

<div class="vb-storage-nineteen-demo">
  <div class="vb-storage-nineteen-login" data-vb-storage-nineteen>
    <div class="vb-storage-nineteen-info">
      <span class="vb-storage-nineteen-kicker">Example 19</span>
      <h3>Remember Me Login</h3>
      <p>Save only the email address in localStorage. Passwords are never stored in this demo.</p>
      <div class="vb-storage-nineteen-status" data-login-status>Email not saved yet.</div>
    </div>

    <form class="vb-storage-nineteen-form" data-login-form>
      <label>
        <span>Email address</span>
        <input type="email" data-login-email placeholder="alex@example.com">
      </label>

      <label>
        <span>Password</span>
        <input type="password" data-login-password placeholder="Password is not saved">
      </label>

      <label class="vb-storage-nineteen-check">
        <input type="checkbox" data-remember-email>
        <span>Remember my email on this device</span>
      </label>

      <button type="submit">Demo Login</button>
      <button type="button" class="vb-storage-nineteen-clear" data-clear-login>Clear Saved Email</button>

      <p class="vb-storage-nineteen-note">Security note: this demo saves only the email address, never the password.</p>
    </form>
  </div>
</div>

CSS

.vb-storage-nineteen-demo,
.vb-storage-nineteen-demo * {
  box-sizing: border-box;
}

.vb-storage-nineteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(37, 99, 235, 0.18), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(14, 165, 233, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfeff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-storage-nineteen-login {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(320px, 430px);
  gap: 22px;
  max-width: 1100px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #0f172a;
  box-shadow: 0 28px 90px rgba(15, 23, 42, 0.30);
}

.vb-storage-nineteen-info,
.vb-storage-nineteen-form {
  min-width: 0;
}

.vb-storage-nineteen-info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 26px);
}

.vb-storage-nineteen-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(59, 130, 246, 0.18);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-nineteen-info h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-nineteen-info p {
  max-width: 560px;
  margin: 0 0 22px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-nineteen-status {
  display: inline-flex;
  align-self: flex-start;
  padding: 13px 15px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 850;
}

.vb-storage-nineteen-form {
  display: grid;
  gap: 14px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-nineteen-form label {
  display: grid;
  gap: 7px;
}

.vb-storage-nineteen-form label > span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-nineteen-form input[type="email"],
.vb-storage-nineteen-form input[type="password"] {
  width: 100%;
  min-height: 50px;
  padding: 0 15px;
  border: 1px solid rgba(148, 163, 184, 0.34);
  border-radius: 16px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 700;
  outline: none;
}

.vb-storage-nineteen-form input:focus {
  border-color: rgba(37, 99, 235, 0.74);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.10);
}

.vb-storage-nineteen-check {
  display: grid !important;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 10px !important;
  padding: 12px;
  border-radius: 16px;
  background: #eff6ff;
}

.vb-storage-nineteen-check input {
  width: 18px;
  height: 18px;
  accent-color: #2563eb;
}

.vb-storage-nineteen-form button {
  min-height: 48px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #0ea5e9);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-nineteen-clear {
  background: #fee2e2 !important;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-storage-nineteen-note {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.55;
  font-weight: 700;
}

@media (max-width: 900px) {
  .vb-storage-nineteen-login {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-nineteen-info h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This remember me localStorage example is useful for login pages, account forms, dashboard prototypes, membership websites, app interfaces, and JavaScript form preference demos. In production, only harmless preferences should be saved in localStorage.

20. Dashboard Layout Saved with LocalStorage

Saving a dashboard layout with localStorage is useful for admin panels, SaaS dashboards, analytics pages, project management tools, ecommerce back offices, and user portals. It lets users choose which widgets are visible and keeps that layout after refresh.

This example lets users toggle dashboard widgets on or off. JavaScript saves the visible widget list in localStorage, updates the dashboard instantly, and restores the selected layout after page reload.

Dashboard Preview 4 widgets visible
Sales €12.4k

Monthly revenue overview

Traffic 48.2k

Website visits this month

Tasks 17

Open project tasks

Messages 9

Unread client messages

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twenty]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentyDashboardLayout";
  const toggles = wrapper.querySelectorAll("[data-widget-toggle]");
  const widgets = wrapper.querySelectorAll("[data-dashboard-widget]");
  const status = wrapper.querySelector("[data-layout-status]");
  const resetButton = wrapper.querySelector("[data-reset-layout]");

  const defaultVisibleWidgets = ["sales", "traffic", "tasks", "messages"];
  let visibleWidgets = defaultVisibleWidgets.slice();

  function saveLayout() {
    localStorage.setItem(storageKey, JSON.stringify(visibleWidgets));
  }

  function loadLayout() {
    const savedLayout = localStorage.getItem(storageKey);
    if (!savedLayout) return;

    try {
      const parsedLayout = JSON.parse(savedLayout);
      visibleWidgets = Array.isArray(parsedLayout) ? parsedLayout : defaultVisibleWidgets.slice();
    } catch (error) {
      visibleWidgets = defaultVisibleWidgets.slice();
      localStorage.removeItem(storageKey);
    }
  }

  function applyLayout() {
    toggles.forEach(function (toggle) {
      const key = toggle.getAttribute("data-widget-toggle");
      toggle.checked = visibleWidgets.includes(key);
    });

    widgets.forEach(function (widget) {
      const key = widget.getAttribute("data-dashboard-widget");
      widget.classList.toggle("is-hidden", !visibleWidgets.includes(key));
    });

    status.textContent = visibleWidgets.length === 1
      ? "1 widget visible"
      : visibleWidgets.length + " widgets visible";

    saveLayout();
  }

  toggles.forEach(function (toggle) {
    toggle.addEventListener("change", function () {
      const key = toggle.getAttribute("data-widget-toggle");

      if (toggle.checked && !visibleWidgets.includes(key)) {
        visibleWidgets.push(key);
      }

      if (!toggle.checked) {
        visibleWidgets = visibleWidgets.filter(function (item) {
          return item !== key;
        });
      }

      applyLayout();
    });
  });

  resetButton.addEventListener("click", function () {
    visibleWidgets = defaultVisibleWidgets.slice();
    localStorage.removeItem(storageKey);
    applyLayout();
  });

  loadLayout();
  applyLayout();
})();

HTML

<div class="vb-storage-twenty-demo">
  <div class="vb-storage-twenty-dashboard" data-vb-storage-twenty>
    <aside class="vb-storage-twenty-controls">
      <span class="vb-storage-twenty-kicker">Example 20</span>
      <h3>Saved Dashboard Layout</h3>
      <p>Choose which widgets should be visible. localStorage saves the dashboard layout after refresh.</p>

      <label><input type="checkbox" data-widget-toggle="sales" checked> Sales widget</label>
      <label><input type="checkbox" data-widget-toggle="traffic" checked> Traffic widget</label>
      <label><input type="checkbox" data-widget-toggle="tasks" checked> Tasks widget</label>
      <label><input type="checkbox" data-widget-toggle="messages" checked> Messages widget</label>

      <button type="button" data-reset-layout>Reset Layout</button>
    </aside>

    <section class="vb-storage-twenty-preview">
      <div class="vb-storage-twenty-preview-head">
        <strong>Dashboard Preview</strong>
        <span data-layout-status>4 widgets visible</span>
      </div>

      <div class="vb-storage-twenty-grid">
        <article data-dashboard-widget="sales">
          <span>Sales</span>
          <strong>€12.4k</strong>
          <p>Monthly revenue overview</p>
        </article>

        <article data-dashboard-widget="traffic">
          <span>Traffic</span>
          <strong>48.2k</strong>
          <p>Website visits this month</p>
        </article>

        <article data-dashboard-widget="tasks">
          <span>Tasks</span>
          <strong>17</strong>
          <p>Open project tasks</p>
        </article>

        <article data-dashboard-widget="messages">
          <span>Messages</span>
          <strong>9</strong>
          <p>Unread client messages</p>
        </article>
      </div>
    </section>
  </div>
</div>

CSS

.vb-storage-twenty-demo,
.vb-storage-twenty-demo * {
  box-sizing: border-box;
}

.vb-storage-twenty-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(99, 102, 241, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(20, 184, 166, 0.14), transparent 34%),
    linear-gradient(135deg, #eef2ff 0%, #f0fdfa 56%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.18);
  box-shadow: 0 28px 80px rgba(67, 56, 202, 0.10);
}

.vb-storage-twenty-dashboard {
  display: grid;
  grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-twenty-controls {
  display: grid;
  align-content: start;
  gap: 12px;
  min-width: 0;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 28px;
  background: #111827;
}

.vb-storage-twenty-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(129, 140, 248, 0.18);
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twenty-controls h3 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 58px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-storage-twenty-controls p {
  margin: 0 0 8px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-storage-twenty-controls label {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  min-height: 44px;
  padding: 10px 12px;
  border-radius: 16px;
  background: rgba(255,255,255,0.08);
  color: #e5e7eb !important;
  -webkit-text-fill-color: #e5e7eb !important;
  font-size: 14px;
  font-weight: 850;
}

.vb-storage-twenty-controls input {
  width: 18px;
  height: 18px;
  accent-color: #6366f1;
}

.vb-storage-twenty-controls button {
  min-height: 44px;
  margin-top: 4px;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twenty-preview {
  display: grid;
  gap: 16px;
  min-width: 0;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-twenty-preview-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.vb-storage-twenty-preview-head strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-twenty-preview-head span {
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-twenty-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-height: 390px;
}

.vb-storage-twenty-grid article {
  display: grid;
  align-content: space-between;
  min-height: 180px;
  padding: 18px;
  border-radius: 24px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.07);
}

.vb-storage-twenty-grid article.is-hidden {
  display: none;
}

.vb-storage-twenty-grid article span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.vb-storage-twenty-grid article strong {
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: clamp(34px, 5vw, 54px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.06em;
}

.vb-storage-twenty-grid article p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

@media (max-width: 920px) {
  .vb-storage-twenty-dashboard {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-twenty-grid {
    grid-template-columns: 1fr;
  }

  .vb-storage-twenty-controls h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This dashboard layout localStorage example is useful for admin panels, analytics dashboards, SaaS apps, ecommerce back offices, CRM interfaces, project management tools, and customizable user portals.

21. Kanban Board Saved with LocalStorage

A Kanban board saved with localStorage is a practical JavaScript project for task management apps, project dashboards, content planning boards, CRM pipelines, and personal productivity tools. It teaches how to save grouped data and move items between columns.

This example lets users add a task, move tasks between To Do, Doing, and Done columns, delete tasks, and restore the full board after refresh. JavaScript stores the board data as a JSON object in localStorage.

Example 21

Saved Kanban Board

Add tasks and move them between columns. localStorage restores the whole board after refresh.

To Do 0

Doing 0

Done 0

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentyone]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentyoneKanban";
  const form = wrapper.querySelector("[data-kanban-form]");
  const input = wrapper.querySelector("[data-kanban-input]");
  const clearButton = wrapper.querySelector("[data-clear-board]");
  const columns = {
    todo: wrapper.querySelector('[data-task-column="todo"]'),
    doing: wrapper.querySelector('[data-task-column="doing"]'),
    done: wrapper.querySelector('[data-task-column="done"]')
  };
  const counts = {
    todo: wrapper.querySelector('[data-count="todo"]'),
    doing: wrapper.querySelector('[data-count="doing"]'),
    done: wrapper.querySelector('[data-count="done"]')
  };

  let board = {
    todo: [],
    doing: [],
    done: []
  };

  function saveBoard() {
    localStorage.setItem(storageKey, JSON.stringify(board));
  }

  function loadBoard() {
    const savedBoard = localStorage.getItem(storageKey);
    if (!savedBoard) return;

    try {
      const parsedBoard = JSON.parse(savedBoard);
      board = {
        todo: Array.isArray(parsedBoard.todo) ? parsedBoard.todo : [],
        doing: Array.isArray(parsedBoard.doing) ? parsedBoard.doing : [],
        done: Array.isArray(parsedBoard.done) ? parsedBoard.done : []
      };
    } catch (error) {
      localStorage.removeItem(storageKey);
    }
  }

  function findTask(taskId) {
    for (const columnName in board) {
      const task = board[columnName].find(function (item) {
        return item.id === taskId;
      });

      if (task) {
        return {
          task: task,
          columnName: columnName
        };
      }
    }

    return null;
  }

  function renderBoard() {
    Object.keys(columns).forEach(function (columnName) {
      columns[columnName].innerHTML = "";
      counts[columnName].textContent = board[columnName].length;

      board[columnName].forEach(function (task) {
        const card = document.createElement("div");
        card.className = "vb-storage-twentyone-task";

        card.innerHTML =
          '<strong>' + task.text + '</strong>' +
          '<div class="vb-storage-twentyone-actions">' +
            '<button type="button" data-move-task="' + task.id + '" data-target-column="todo">To Do</button>' +
            '<button type="button" data-move-task="' + task.id + '" data-target-column="doing">Doing</button>' +
            '<button type="button" data-move-task="' + task.id + '" data-target-column="done">Done</button>' +
            '<button type="button" data-delete-task="' + task.id + '">Delete</button>' +
          '</div>';

        columns[columnName].appendChild(card);
      });
    });

    saveBoard();
  }

  function addTask(text) {
    board.todo.unshift({
      id: String(Date.now()),
      text: text
    });

    renderBoard();
  }

  function moveTask(taskId, targetColumn) {
    const found = findTask(taskId);
    if (!found || found.columnName === targetColumn) return;

    board[found.columnName] = board[found.columnName].filter(function (task) {
      return task.id !== taskId;
    });

    board[targetColumn].unshift(found.task);
    renderBoard();
  }

  function deleteTask(taskId) {
    Object.keys(board).forEach(function (columnName) {
      board[columnName] = board[columnName].filter(function (task) {
        return task.id !== taskId;
      });
    });

    renderBoard();
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();

    const text = input.value.trim();
    if (!text) return;

    addTask(text);
    input.value = "";
    input.focus();
  });

  wrapper.addEventListener("click", function (event) {
    const moveId = event.target.getAttribute("data-move-task");
    const targetColumn = event.target.getAttribute("data-target-column");
    const deleteId = event.target.getAttribute("data-delete-task");

    if (moveId && targetColumn) {
      moveTask(moveId, targetColumn);
    }

    if (deleteId) {
      deleteTask(deleteId);
    }
  });

  clearButton.addEventListener("click", function () {
    board = {
      todo: [],
      doing: [],
      done: []
    };

    localStorage.removeItem(storageKey);
    renderBoard();
  });

  loadBoard();
  renderBoard();
})();

HTML

<div class="vb-storage-twentyone-demo">
  <div class="vb-storage-twentyone-board" data-vb-storage-twentyone>
    <div class="vb-storage-twentyone-head">
      <span class="vb-storage-twentyone-kicker">Example 21</span>
      <h3>Saved Kanban Board</h3>
      <p>Add tasks and move them between columns. localStorage restores the whole board after refresh.</p>
    </div>

    <form class="vb-storage-twentyone-form" data-kanban-form>
      <input type="text" data-kanban-input placeholder="Add a new task...">
      <button type="submit">Add Task</button>
      <button type="button" data-clear-board>Clear Board</button>
    </form>

    <div class="vb-storage-twentyone-columns">
      <section data-column="todo">
        <h4>To Do <span data-count="todo">0</span></h4>
        <div data-task-column="todo"></div>
      </section>

      <section data-column="doing">
        <h4>Doing <span data-count="doing">0</span></h4>
        <div data-task-column="doing"></div>
      </section>

      <section data-column="done">
        <h4>Done <span data-count="done">0</span></h4>
        <div data-task-column="done"></div>
      </section>
    </div>
  </div>
</div>

CSS

.vb-storage-twentyone-demo,
.vb-storage-twentyone-demo * {
  box-sizing: border-box;
}

.vb-storage-twentyone-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(168, 85, 247, 0.16), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #faf5ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  box-shadow: 0 28px 80px rgba(12, 74, 110, 0.10);
}

.vb-storage-twentyone-board {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #0f172a;
  box-shadow: 0 28px 90px rgba(15, 23, 42, 0.30);
}

.vb-storage-twentyone-head {
  display: grid;
  gap: 12px;
  margin-bottom: 20px;
}

.vb-storage-twentyone-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(14, 165, 233, 0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentyone-head h3 {
  max-width: 820px;
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twentyone-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentyone-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 10px;
  margin-bottom: 18px;
  padding: 14px;
  border-radius: 24px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-twentyone-form input {
  min-width: 0;
  min-height: 48px;
  padding: 0 15px;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 750;
  outline: none;
}

.vb-storage-twentyone-form input::placeholder {
  color: #9ca3af;
  -webkit-text-fill-color: #9ca3af;
}

.vb-storage-twentyone-form button {
  min-height: 48px;
  padding: 0 15px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #0ea5e9, #9333ea);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentyone-form button:last-child {
  background: rgba(255,255,255,0.12);
}

.vb-storage-twentyone-columns {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}

.vb-storage-twentyone-columns section {
  display: grid;
  align-content: start;
  gap: 12px;
  min-height: 390px;
  padding: 14px;
  border-radius: 26px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-storage-twentyone-columns h4 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px !important;
  line-height: 1.15 !important;
  font-weight: 950 !important;
}

.vb-storage-twentyone-columns h4 span {
  display: grid;
  place-items: center;
  min-width: 34px;
  height: 34px;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-twentyone-columns [data-task-column] {
  display: grid;
  gap: 10px;
}

.vb-storage-twentyone-task {
  display: grid;
  gap: 10px;
  padding: 13px;
  border-radius: 18px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 10px 26px rgba(15, 23, 42, 0.06);
}

.vb-storage-twentyone-task strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  line-height: 1.35;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-storage-twentyone-actions {
  display: flex;
  gap: 7px;
  flex-wrap: wrap;
}

.vb-storage-twentyone-actions button {
  min-height: 32px;
  padding: 6px 9px;
  border: 0;
  border-radius: 999px;
  background: #e0e7ff;
  color: #3730a3 !important;
  -webkit-text-fill-color: #3730a3 !important;
  font-size: 11px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentyone-actions button:last-child {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

@media (max-width: 940px) {
  .vb-storage-twentyone-columns {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-twentyone-form {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentyone-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This Kanban board localStorage example is useful for task managers, project dashboards, content planning boards, CRM pipeline demos, productivity apps, and JavaScript portfolio projects that need persistent grouped data.

22. Shopping Cart Saved with LocalStorage

A shopping cart saved with localStorage is one of the most practical JavaScript localStorage examples for ecommerce websites, product landing pages, digital product shops, affiliate demos, and cart UI prototypes. It lets visitors add products to a cart and keep the cart after refresh.

This example lets users add products, increase or decrease quantity, remove items, clear the cart, and restore the cart from localStorage after page reload. JavaScript stores the cart as a JSON array and recalculates the total every time the cart changes.

Example 22

Saved Shopping Cart

Add products to the cart, refresh the page, and localStorage restores the full cart with quantities.

WEB

Website Template

Responsive landing page template for a modern website.

49€
JS

JavaScript Plugin

Small browser feature that saves user actions locally.

29€
SEO

SEO Audit

Technical SEO review for structure, speed, and content.

99€

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentytwo]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentytwoCart";
  const products = wrapper.querySelectorAll("[data-product-id]");
  const cartList = wrapper.querySelector("[data-cart-list]");
  const cartCount = wrapper.querySelector("[data-cart-count]");
  const cartTotal = wrapper.querySelector("[data-cart-total]");
  const clearButton = wrapper.querySelector("[data-clear-cart]");

  let cart = [];

  function saveCart() {
    localStorage.setItem(storageKey, JSON.stringify(cart));
  }

  function loadCart() {
    const savedCart = localStorage.getItem(storageKey);
    if (!savedCart) return;

    try {
      const parsedCart = JSON.parse(savedCart);
      cart = Array.isArray(parsedCart) ? parsedCart : [];
    } catch (error) {
      cart = [];
      localStorage.removeItem(storageKey);
    }
  }

  function renderCart() {
    cartList.innerHTML = "";

    const totalItems = cart.reduce(function (sum, item) {
      return sum + item.quantity;
    }, 0);

    const subtotal = cart.reduce(function (sum, item) {
      return sum + item.price * item.quantity;
    }, 0);

    cartCount.textContent = totalItems === 1 ? "1 item" : totalItems + " items";
    cartTotal.textContent = subtotal + "€";

    if (cart.length === 0) {
      cartList.innerHTML = '<p class="vb-storage-twentytwo-empty">Your cart is empty.</p>';
      saveCart();
      return;
    }

    cart.forEach(function (item) {
      const row = document.createElement("div");
      row.className = "vb-storage-twentytwo-cart-item";

      row.innerHTML =
        '<div class="vb-storage-twentytwo-cart-item-top">' +
          '<strong>' + item.name + '</strong>' +
          '<span>' + item.price + '€</span>' +
        '</div>' +
        '<div class="vb-storage-twentytwo-qty">' +
          '<button type="button" data-decrease="' + item.id + '">−</button>' +
          '<small>Qty ' + item.quantity + '</small>' +
          '<button type="button" data-increase="' + item.id + '">+</button>' +
          '<button type="button" data-remove="' + item.id + '">×</button>' +
        '</div>';

      cartList.appendChild(row);
    });

    saveCart();
  }

  function addToCart(product) {
    const productId = product.getAttribute("data-product-id");
    const existingItem = cart.find(function (item) {
      return item.id === productId;
    });

    if (existingItem) {
      existingItem.quantity += 1;
    } else {
      cart.push({
        id: productId,
        name: product.getAttribute("data-product-name"),
        price: Number(product.getAttribute("data-product-price")),
        quantity: 1
      });
    }

    renderCart();
  }

  function changeQuantity(productId, amount) {
    cart = cart.map(function (item) {
      if (item.id === productId) {
        item.quantity += amount;
      }

      return item;
    }).filter(function (item) {
      return item.quantity > 0;
    });

    renderCart();
  }

  function removeItem(productId) {
    cart = cart.filter(function (item) {
      return item.id !== productId;
    });

    renderCart();
  }

  products.forEach(function (product) {
    product.querySelector("[data-add-cart]").addEventListener("click", function () {
      addToCart(product);
    });
  });

  cartList.addEventListener("click", function (event) {
    const increaseId = event.target.getAttribute("data-increase");
    const decreaseId = event.target.getAttribute("data-decrease");
    const removeId = event.target.getAttribute("data-remove");

    if (increaseId) changeQuantity(increaseId, 1);
    if (decreaseId) changeQuantity(decreaseId, -1);
    if (removeId) removeItem(removeId);
  });

  clearButton.addEventListener("click", function () {
    cart = [];
    localStorage.removeItem(storageKey);
    renderCart();
  });

  loadCart();
  renderCart();
})();

HTML

<div class="vb-storage-twentytwo-demo">
  <div class="vb-storage-twentytwo-shop" data-vb-storage-twentytwo>
    <div class="vb-storage-twentytwo-head">
      <span class="vb-storage-twentytwo-kicker">Example 22</span>
      <h3>Saved Shopping Cart</h3>
      <p>Add products to the cart, refresh the page, and localStorage restores the full cart with quantities.</p>
    </div>

    <div class="vb-storage-twentytwo-layout">
      <div class="vb-storage-twentytwo-products">
        <article data-product-id="template" data-product-name="Website Template" data-product-price="49">
          <span>WEB</span>
          <h4>Website Template</h4>
          <p>Responsive landing page template for a modern website.</p>
          <strong>49€</strong>
          <button type="button" data-add-cart>Add to Cart</button>
        </article>

        <article data-product-id="plugin" data-product-name="JavaScript Plugin" data-product-price="29">
          <span>JS</span>
          <h4>JavaScript Plugin</h4>
          <p>Small browser feature that saves user actions locally.</p>
          <strong>29€</strong>
          <button type="button" data-add-cart>Add to Cart</button>
        </article>

        <article data-product-id="audit" data-product-name="SEO Audit" data-product-price="99">
          <span>SEO</span>
          <h4>SEO Audit</h4>
          <p>Technical SEO review for structure, speed, and content.</p>
          <strong>99€</strong>
          <button type="button" data-add-cart>Add to Cart</button>
        </article>
      </div>

      <aside class="vb-storage-twentytwo-cart">
        <div class="vb-storage-twentytwo-cart-head">
          <strong>Your Cart</strong>
          <span data-cart-count>0 items</span>
        </div>

        <div class="vb-storage-twentytwo-cart-list" data-cart-list>
          <p class="vb-storage-twentytwo-empty">Your cart is empty.</p>
        </div>

        <div class="vb-storage-twentytwo-total">
          <span>Subtotal</span>
          <strong data-cart-total>0€</strong>
        </div>

        <button type="button" data-clear-cart>Clear Cart</button>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-storage-twentytwo-demo,
.vb-storage-twentytwo-demo * {
  box-sizing: border-box;
}

.vb-storage-twentytwo-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(16, 185, 129, 0.18), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #ecfdf5 0%, #eff6ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(16, 185, 129, 0.18);
  box-shadow: 0 28px 80px rgba(6, 95, 70, 0.10);
}

.vb-storage-twentytwo-shop {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-twentytwo-head {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-twentytwo-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #dcfce7;
  color: #15803d !important;
  -webkit-text-fill-color: #15803d !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentytwo-head h3 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twentytwo-head p {
  max-width: 780px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentytwo-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 410px);
  gap: 20px;
  align-items: start;
}

.vb-storage-twentytwo-products {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}

.vb-storage-twentytwo-products article {
  display: grid;
  align-content: start;
  gap: 11px;
  min-height: 280px;
  padding: 18px;
  border-radius: 26px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-storage-twentytwo-products article > span {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: 22px;
  background: linear-gradient(135deg, #10b981, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 16px;
  font-weight: 950;
}

.vb-storage-twentytwo-products h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
}

.vb-storage-twentytwo-products p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-storage-twentytwo-products strong {
  margin-top: auto;
  color: #059669 !important;
  -webkit-text-fill-color: #059669 !important;
  font-size: 26px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-twentytwo-products button,
.vb-storage-twentytwo-cart > button {
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentytwo-products button {
  background: linear-gradient(135deg, #10b981, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-twentytwo-cart {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 28px;
  background: #0f172a;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.24);
}

.vb-storage-twentytwo-cart-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-twentytwo-cart-head strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-twentytwo-cart-head span {
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-twentytwo-cart-list {
  display: grid;
  gap: 10px;
  min-height: 280px;
}

.vb-storage-twentytwo-empty {
  display: grid;
  place-items: center;
  min-height: 280px;
  margin: 0 !important;
  border: 1px dashed rgba(255,255,255,0.22);
  border-radius: 20px;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-twentytwo-cart-item {
  display: grid;
  gap: 10px;
  padding: 12px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-twentytwo-cart-item-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.vb-storage-twentytwo-cart-item strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.25;
  font-weight: 900;
}

.vb-storage-twentytwo-cart-item span {
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-twentytwo-qty {
  display: flex;
  align-items: center;
  gap: 7px;
}

.vb-storage-twentytwo-qty button {
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentytwo-qty small {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-twentytwo-total {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px;
  border-radius: 20px;
  background: rgba(255,255,255,0.10);
}

.vb-storage-twentytwo-total span {
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-storage-twentytwo-total strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 34px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-twentytwo-cart > button {
  background: #ffffff;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
}

@media (max-width: 980px) {
  .vb-storage-twentytwo-layout {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentytwo-products {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .vb-storage-twentytwo-products {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentytwo-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This shopping cart localStorage example is useful for ecommerce demos, WooCommerce-style product pages, digital product shops, checkout prototypes, mini cart features, and JavaScript portfolio projects.

23. Form Autosave with LocalStorage

Form autosave with localStorage is useful for contact forms, quote request forms, application forms, booking forms, support tickets, long surveys, and checkout flows. It prevents users from losing typed content if they accidentally refresh the page.

This example automatically saves form fields while the user types. JavaScript restores the draft from localStorage after refresh, shows a saved status message, and lets the user clear the saved draft when needed.

Example 23

Form Autosave Draft

Type into the form and refresh the page. localStorage restores the saved draft automatically.

Draft status: ready.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentythree]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentythreeFormDraft";
  const form = wrapper.querySelector("[data-autosave-form]");
  const fields = Array.from(form.querySelectorAll("input, select, textarea"));
  const status = wrapper.querySelector("[data-draft-status]");
  const clearButton = wrapper.querySelector("[data-clear-draft]");
  let saveTimer;

  function getDraftData() {
    const data = {};

    fields.forEach(function (field) {
      data[field.name] = field.value;
    });

    return data;
  }

  function saveDraft() {
    localStorage.setItem(storageKey, JSON.stringify(getDraftData()));
    status.textContent = "Draft status: saved at " + new Date().toLocaleTimeString();
  }

  function loadDraft() {
    const savedDraft = localStorage.getItem(storageKey);
    if (!savedDraft) return;

    try {
      const data = JSON.parse(savedDraft);

      fields.forEach(function (field) {
        field.value = data[field.name] || "";
      });

      status.textContent = "Draft status: restored from localStorage.";
    } catch (error) {
      localStorage.removeItem(storageKey);
      status.textContent = "Draft status: saved draft could not be loaded.";
    }
  }

  function scheduleSave() {
    status.textContent = "Draft status: saving...";
    clearTimeout(saveTimer);
    saveTimer = setTimeout(saveDraft, 350);
  }

  fields.forEach(function (field) {
    field.addEventListener("input", scheduleSave);
    field.addEventListener("change", scheduleSave);
  });

  form.addEventListener("submit", function (event) {
    event.preventDefault();
    localStorage.removeItem(storageKey);
    status.textContent = "Demo submitted. Saved draft has been cleared.";
    form.reset();
  });

  clearButton.addEventListener("click", function () {
    localStorage.removeItem(storageKey);
    form.reset();
    status.textContent = "Draft status: cleared.";
  });

  loadDraft();
})();

HTML

<div class="vb-storage-twentythree-demo">
  <div class="vb-storage-twentythree-formwrap" data-vb-storage-twentythree>
    <div class="vb-storage-twentythree-intro">
      <span class="vb-storage-twentythree-kicker">Example 23</span>
      <h3>Form Autosave Draft</h3>
      <p>Type into the form and refresh the page. localStorage restores the saved draft automatically.</p>

      <div class="vb-storage-twentythree-status" data-draft-status>
        Draft status: ready.
      </div>
    </div>

    <form class="vb-storage-twentythree-form" data-autosave-form>
      <label>
        <span>Your name</span>
        <input type="text" name="name" placeholder="Your name">
      </label>

      <label>
        <span>Email address</span>
        <input type="email" name="email" placeholder="you@example.com">
      </label>

      <label>
        <span>Project type</span>
        <select name="projectType">
          <option value="">Choose project type</option>
          <option value="Website">Website</option>
          <option value="Ecommerce">Ecommerce</option>
          <option value="WordPress Plugin">WordPress Plugin</option>
          <option value="JavaScript Feature">JavaScript Feature</option>
        </select>
      </label>

      <label>
        <span>Project message</span>
        <textarea name="message" rows="6" placeholder="Describe what you need..."></textarea>
      </label>

      <div class="vb-storage-twentythree-actions">
        <button type="submit">Submit Demo</button>
        <button type="button" data-clear-draft>Clear Saved Draft</button>
      </div>
    </form>
  </div>
</div>

CSS

.vb-storage-twentythree-demo,
.vb-storage-twentythree-demo * {
  box-sizing: border-box;
}

.vb-storage-twentythree-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(236, 72, 153, 0.15), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #fdf2f8 0%, #eef2ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(236, 72, 153, 0.18);
  box-shadow: 0 28px 80px rgba(157, 23, 77, 0.10);
}

.vb-storage-twentythree-formwrap {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-twentythree-intro,
.vb-storage-twentythree-form {
  min-width: 0;
}

.vb-storage-twentythree-intro {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #111827;
}

.vb-storage-twentythree-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(236, 72, 153, 0.16);
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentythree-intro h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twentythree-intro p {
  max-width: 560px;
  margin: 0 0 22px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentythree-status {
  padding: 13px 15px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 850;
}

.vb-storage-twentythree-form {
  display: grid;
  gap: 14px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-twentythree-form label {
  display: grid;
  gap: 7px;
}

.vb-storage-twentythree-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-twentythree-form input,
.vb-storage-twentythree-form select,
.vb-storage-twentythree-form textarea {
  width: 100%;
  border: 1px solid rgba(148, 163, 184, 0.34);
  border-radius: 16px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 700;
  outline: none;
}

.vb-storage-twentythree-form input,
.vb-storage-twentythree-form select {
  min-height: 50px;
  padding: 0 15px;
}

.vb-storage-twentythree-form textarea {
  resize: vertical;
  padding: 14px 15px;
  line-height: 1.55;
}

.vb-storage-twentythree-form input:focus,
.vb-storage-twentythree-form select:focus,
.vb-storage-twentythree-form textarea:focus {
  border-color: rgba(236, 72, 153, 0.70);
  box-shadow: 0 0 0 4px rgba(236, 72, 153, 0.10);
}

.vb-storage-twentythree-actions {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px;
}

.vb-storage-twentythree-actions button {
  min-height: 48px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentythree-actions button:first-child {
  background: linear-gradient(135deg, #ec4899, #6366f1);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-twentythree-actions button:last-child {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

@media (max-width: 900px) {
  .vb-storage-twentythree-formwrap {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 580px) {
  .vb-storage-twentythree-actions {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentythree-intro h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This form autosave localStorage example is useful for contact forms, quote request forms, application forms, checkout forms, booking forms, support tickets, survey forms, and long lead generation forms.

24. Dark Mode Theme Saved with LocalStorage

Saving dark mode with localStorage is one of the most searched JavaScript localStorage examples. It is useful for blogs, dashboards, documentation websites, SaaS interfaces, portfolios, admin panels, and modern landing pages.

This example toggles between light mode and dark mode. JavaScript saves the selected theme in localStorage, applies the theme instantly with a data attribute, and restores the same theme after refresh.

Example 24

Saved Dark Mode Theme

Switch between light and dark mode. localStorage remembers your selected theme after refresh.

Current theme: light
Theme Preview

This preview changes between light and dark mode using one saved localStorage value.

Readability High
Saved Yes

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentyfour]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentyfourTheme";
  const toggleButton = wrapper.querySelector("[data-theme-toggle]");
  const status = wrapper.querySelector("[data-theme-status]");

  function applyTheme(theme) {
    wrapper.setAttribute("data-theme", theme);
    localStorage.setItem(storageKey, theme);
    status.textContent = "Current theme: " + theme;
    toggleButton.textContent = theme === "dark" ? "Switch to Light Mode" : "Switch to Dark Mode";
  }

  toggleButton.addEventListener("click", function () {
    const currentTheme = wrapper.getAttribute("data-theme");
    applyTheme(currentTheme === "dark" ? "light" : "dark");
  });

  applyTheme(localStorage.getItem(storageKey) || "light");
})();

HTML

<div class="vb-storage-twentyfour-demo">
  <div class="vb-storage-twentyfour-theme" data-vb-storage-twentyfour data-theme="light">
    <div class="vb-storage-twentyfour-panel">
      <span class="vb-storage-twentyfour-kicker">Example 24</span>
      <h3>Saved Dark Mode Theme</h3>
      <p>Switch between light and dark mode. localStorage remembers your selected theme after refresh.</p>

      <button type="button" data-theme-toggle>
        Toggle Dark Mode
      </button>

      <div class="vb-storage-twentyfour-status" data-theme-status>
        Current theme: light
      </div>
    </div>

    <div class="vb-storage-twentyfour-preview">
      <div class="vb-storage-twentyfour-window">
        <div class="vb-storage-twentyfour-window-top">
          <span></span>
          <span></span>
          <span></span>
        </div>

        <div class="vb-storage-twentyfour-content">
          <strong>Theme Preview</strong>
          <p>This preview changes between light and dark mode using one saved localStorage value.</p>

          <div class="vb-storage-twentyfour-cards">
            <div>
              <span>Readability</span>
              <strong>High</strong>
            </div>
            <div>
              <span>Saved</span>
              <strong>Yes</strong>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-twentyfour-demo,
.vb-storage-twentyfour-demo * {
  box-sizing: border-box;
}

.vb-storage-twentyfour-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(15, 23, 42, 0.14), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #eef2ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(15, 23, 42, 0.12);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.10);
}

.vb-storage-twentyfour-theme {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1.14fr);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
  transition: background 0.25s ease, border-color 0.25s ease;
}

.vb-storage-twentyfour-theme[data-theme="dark"] {
  background: #020617;
  border-color: rgba(255,255,255,0.10);
}

.vb-storage-twentyfour-panel,
.vb-storage-twentyfour-preview {
  min-width: 0;
}

.vb-storage-twentyfour-panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #0f172a;
}

.vb-storage-twentyfour-theme[data-theme="dark"] .vb-storage-twentyfour-panel {
  background: #111827;
  border: 1px solid rgba(255,255,255,0.10);
}

.vb-storage-twentyfour-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(129, 140, 248, 0.18);
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentyfour-panel h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twentyfour-panel p {
  max-width: 560px;
  margin: 0 0 22px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentyfour-panel button {
  align-self: flex-start;
  min-height: 50px;
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #6366f1, #0ea5e9);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentyfour-status {
  align-self: flex-start;
  margin-top: 14px;
  padding: 12px 14px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 14px;
  font-weight: 850;
}

.vb-storage-twentyfour-preview {
  display: grid;
  place-items: center;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
  transition: background 0.25s ease, border-color 0.25s ease;
}

.vb-storage-twentyfour-theme[data-theme="dark"] .vb-storage-twentyfour-preview {
  background: #0f172a;
  border-color: rgba(255,255,255,0.10);
}

.vb-storage-twentyfour-window {
  width: 100%;
  max-width: 560px;
  overflow: hidden;
  border-radius: 28px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 22px 60px rgba(15, 23, 42, 0.12);
  transition: background 0.25s ease, border-color 0.25s ease;
}

.vb-storage-twentyfour-theme[data-theme="dark"] .vb-storage-twentyfour-window {
  background: #020617;
  border-color: rgba(255,255,255,0.12);
}

.vb-storage-twentyfour-window-top {
  display: flex;
  gap: 8px;
  padding: 16px;
  background: #f1f5f9;
  border-bottom: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-storage-twentyfour-theme[data-theme="dark"] .vb-storage-twentyfour-window-top {
  background: #111827;
  border-bottom-color: rgba(255,255,255,0.10);
}

.vb-storage-twentyfour-window-top span {
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: #f87171;
}

.vb-storage-twentyfour-window-top span:nth-child(2) {
  background: #fbbf24;
}

.vb-storage-twentyfour-window-top span:nth-child(3) {
  background: #34d399;
}

.vb-storage-twentyfour-content {
  display: grid;
  gap: 16px;
  padding: clamp(24px, 5vw, 46px);
}

.vb-storage-twentyfour-content > strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 54px);
  line-height: 0.96;
  font-weight: 950;
  letter-spacing: -0.07em;
}

.vb-storage-twentyfour-theme[data-theme="dark"] .vb-storage-twentyfour-content > strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-twentyfour-content p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentyfour-theme[data-theme="dark"] .vb-storage-twentyfour-content p {
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
}

.vb-storage-twentyfour-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-storage-twentyfour-cards div {
  padding: 16px;
  border-radius: 20px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-twentyfour-theme[data-theme="dark"] .vb-storage-twentyfour-cards div {
  background: #111827;
  border-color: rgba(255,255,255,0.10);
}

.vb-storage-twentyfour-cards span {
  display: block;
  margin-bottom: 7px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-storage-twentyfour-theme[data-theme="dark"] .vb-storage-twentyfour-cards span {
  color: #94a3b8 !important;
  -webkit-text-fill-color: #94a3b8 !important;
}

.vb-storage-twentyfour-cards strong {
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: 22px;
  font-weight: 950;
}

@media (max-width: 900px) {
  .vb-storage-twentyfour-theme {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 560px) {
  .vb-storage-twentyfour-panel h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }

  .vb-storage-twentyfour-panel button {
    width: 100%;
  }

  .vb-storage-twentyfour-cards {
    grid-template-columns: 1fr;
  }
}

This dark mode localStorage example is useful for blogs, dashboards, SaaS interfaces, documentation websites, admin panels, user portals, portfolio websites, and modern JavaScript UI preference features.

Need this on your website?

Can’t build it yourself? We can create it for you.

If you like these JavaScript localStorage features but want a custom version for your website, landing page, ecommerce store, or WordPress project, contact us and tell us what you need.

Contact Us

25. Recent Searches Saved with LocalStorage

Recent searches saved with localStorage are useful for search bars, ecommerce stores, documentation websites, blog search pages, SaaS dashboards, help centers, and directory websites. It helps users quickly repeat previous searches without needing a backend account system.

This example saves submitted search terms into localStorage, displays the latest searches as clickable chips, restores them after refresh, and allows users to remove individual searches or clear the full history.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentyfive]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentyfiveRecentSearches";
  const form = wrapper.querySelector("[data-search-form]");
  const input = wrapper.querySelector("[data-search-input]");
  const searchList = wrapper.querySelector("[data-search-list]");
  const currentSearch = wrapper.querySelector("[data-current-search]");
  const clearButton = wrapper.querySelector("[data-clear-searches]");

  let searches = [];

  function saveSearches() {
    localStorage.setItem(storageKey, JSON.stringify(searches));
  }

  function loadSearches() {
    const savedSearches = localStorage.getItem(storageKey);
    if (!savedSearches) return;

    try {
      const parsedSearches = JSON.parse(savedSearches);
      searches = Array.isArray(parsedSearches) ? parsedSearches : [];
    } catch (error) {
      searches = [];
      localStorage.removeItem(storageKey);
    }
  }

  function renderSearches() {
    searchList.innerHTML = "";

    if (searches.length === 0) {
      searchList.innerHTML = '<p class="vb-storage-twentyfive-empty">No recent searches saved yet.</p>';
      return;
    }

    searches.forEach(function (term) {
      const chip = document.createElement("div");
      chip.className = "vb-storage-twentyfive-chip";

      chip.innerHTML =
        '<button type="button" data-use-search="' + term + '">' + term + '</button>' +
        '<button type="button" data-remove-search="' + term + '">×</button>';

      searchList.appendChild(chip);
    });
  }

  function addSearch(term) {
    const cleanTerm = term.trim();
    if (!cleanTerm) return;

    searches = searches.filter(function (item) {
      return item.toLowerCase() !== cleanTerm.toLowerCase();
    });

    searches.unshift(cleanTerm);
    searches = searches.slice(0, 8);
    currentSearch.textContent = cleanTerm;
    input.value = cleanTerm;

    saveSearches();
    renderSearches();
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();
    addSearch(input.value);
  });

  searchList.addEventListener("click", function (event) {
    const useSearch = event.target.getAttribute("data-use-search");
    const removeSearch = event.target.getAttribute("data-remove-search");

    if (useSearch) {
      currentSearch.textContent = useSearch;
      input.value = useSearch;
    }

    if (removeSearch) {
      searches = searches.filter(function (item) {
        return item !== removeSearch;
      });

      saveSearches();
      renderSearches();
    }
  });

  clearButton.addEventListener("click", function () {
    searches = [];
    localStorage.removeItem(storageKey);
    currentSearch.textContent = "No search yet";
    input.value = "";
    renderSearches();
  });

  loadSearches();
  renderSearches();
})();

HTML

<div class="vb-storage-twentyfive-demo">
  <div class="vb-storage-twentyfive-search" data-vb-storage-twentyfive>
    <div class="vb-storage-twentyfive-head">
      <span class="vb-storage-twentyfive-kicker">Example 25</span>
      <h3>Saved Recent Searches</h3>
      <p>Search for something, refresh the page, and localStorage keeps your recent search history.</p>
    </div>

    <form class="vb-storage-twentyfive-form" data-search-form>
      <input type="search" data-search-input placeholder="Search tutorials, products, docs...">
      <button type="submit">Search</button>
    </form>

    <div class="vb-storage-twentyfive-current">
      <span>Current search</span>
      <strong data-current-search>No search yet</strong>
    </div>

    <div class="vb-storage-twentyfive-history">
      <div class="vb-storage-twentyfive-history-head">
        <strong>Recent Searches</strong>
        <button type="button" data-clear-searches>Clear All</button>
      </div>

      <div class="vb-storage-twentyfive-list" data-search-list>
        <p class="vb-storage-twentyfive-empty">No recent searches saved yet.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-storage-twentyfive-demo,
.vb-storage-twentyfive-demo * {
  box-sizing: border-box;
}

.vb-storage-twentyfive-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(34, 197, 94, 0.14), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #f0fdf4 56%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  box-shadow: 0 28px 80px rgba(12, 74, 110, 0.10);
}

.vb-storage-twentyfive-search {
  max-width: 980px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #0f172a;
  box-shadow: 0 28px 90px rgba(15, 23, 42, 0.30);
}

.vb-storage-twentyfive-head {
  display: grid;
  gap: 12px;
  margin-bottom: 22px;
}

.vb-storage-twentyfive-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(14, 165, 233, 0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentyfive-head h3 {
  max-width: 780px;
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twentyfive-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentyfive-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  margin-bottom: 16px;
  padding: 14px;
  border-radius: 24px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-twentyfive-form input {
  min-width: 0;
  min-height: 52px;
  padding: 0 16px;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-storage-twentyfive-form input::placeholder {
  color: #9ca3af;
  -webkit-text-fill-color: #9ca3af;
}

.vb-storage-twentyfive-form input:focus {
  border-color: rgba(14, 165, 233, 0.72);
  box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.12);
}

.vb-storage-twentyfive-form button {
  min-height: 52px;
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #0ea5e9, #22c55e);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentyfive-current {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 16px;
  padding: 15px;
  border-radius: 20px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-twentyfive-current span {
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-twentyfive-current strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
}

.vb-storage-twentyfive-history {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 28px;
  background: #ffffff;
}

.vb-storage-twentyfive-history-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.vb-storage-twentyfive-history-head strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-twentyfive-history-head button {
  min-height: 38px;
  padding: 0 13px;
  border: 0;
  border-radius: 999px;
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentyfive-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  min-height: 150px;
  align-content: flex-start;
}

.vb-storage-twentyfive-empty {
  display: grid;
  place-items: center;
  width: 100%;
  min-height: 150px;
  margin: 0 !important;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  border-radius: 20px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-twentyfive-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 42px;
  padding: 8px 10px 8px 14px;
  border-radius: 999px;
  background: #f0f9ff;
  border: 1px solid rgba(14, 165, 233, 0.20);
}

.vb-storage-twentyfive-chip button:first-child {
  border: 0;
  background: transparent;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 13px;
  font-weight: 900;
  cursor: pointer;
}

.vb-storage-twentyfive-chip button:last-child {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
}

@media (max-width: 640px) {
  .vb-storage-twentyfive-form {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentyfive-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This recent searches localStorage example is useful for ecommerce search bars, blog search pages, documentation websites, SaaS dashboards, help centers, directory websites, and product finder interfaces.

26. Recently Viewed Products Saved with LocalStorage

Recently viewed products saved with localStorage are useful for ecommerce stores, WooCommerce product pages, affiliate websites, marketplaces, real estate listings, booking platforms, and product recommendation sections. It helps visitors return to items they already explored.

This example lets users click product cards to mark them as viewed. JavaScript saves recently viewed products in localStorage, displays them in a separate panel, avoids duplicates, and restores the same list after refresh.

Example 26

Recently Viewed Products

Click product cards to add them to the viewed list. Refresh the page and your viewed products stay saved.

CH

Cloud Hosting

Fast hosting package for business websites.

29€
SEO

SEO Audit

Technical review for rankings and structure.

99€
WP

Custom Plugin

WordPress plugin built for custom workflows.

399€
UI

Landing Page

Modern responsive sales page section.

249€

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentysix]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentysixViewedProducts";
  const products = Array.from(wrapper.querySelectorAll("[data-product]"));
  const viewedList = wrapper.querySelector("[data-viewed-list]");
  const clearButton = wrapper.querySelector("[data-clear-viewed]");

  let viewedProducts = [];

  function saveViewedProducts() {
    localStorage.setItem(storageKey, JSON.stringify(viewedProducts));
  }

  function loadViewedProducts() {
    const savedProducts = localStorage.getItem(storageKey);
    if (!savedProducts) return;

    try {
      const parsedProducts = JSON.parse(savedProducts);
      viewedProducts = Array.isArray(parsedProducts) ? parsedProducts : [];
    } catch (error) {
      viewedProducts = [];
      localStorage.removeItem(storageKey);
    }
  }

  function getProductData(product) {
    return {
      id: product.getAttribute("data-id"),
      name: product.getAttribute("data-name"),
      price: product.getAttribute("data-price"),
      icon: product.getAttribute("data-icon")
    };
  }

  function renderViewedProducts() {
    viewedList.innerHTML = "";

    products.forEach(function (product) {
      const productId = product.getAttribute("data-id");
      const isViewed = viewedProducts.some(function (item) {
        return item.id === productId;
      });

      product.classList.toggle("is-viewed", isViewed);
    });

    if (viewedProducts.length === 0) {
      viewedList.innerHTML = '<p class="vb-storage-twentysix-empty">No viewed products yet.</p>';
      return;
    }

    viewedProducts.forEach(function (product) {
      const item = document.createElement("div");
      item.className = "vb-storage-twentysix-viewed";

      item.innerHTML =
        '<span>' + product.icon + '</span>' +
        '<div>' +
          '<strong>' + product.name + '</strong>' +
          '<small>' + product.price + '</small>' +
        '</div>';

      viewedList.appendChild(item);
    });
  }

  function addViewedProduct(product) {
    const productData = getProductData(product);

    viewedProducts = viewedProducts.filter(function (item) {
      return item.id !== productData.id;
    });

    viewedProducts.unshift(productData);
    viewedProducts = viewedProducts.slice(0, 5);

    saveViewedProducts();
    renderViewedProducts();
  }

  products.forEach(function (product) {
    product.querySelector("[data-view-product]").addEventListener("click", function () {
      addViewedProduct(product);
    });
  });

  clearButton.addEventListener("click", function () {
    viewedProducts = [];
    localStorage.removeItem(storageKey);
    renderViewedProducts();
  });

  loadViewedProducts();
  renderViewedProducts();
})();

HTML

<div class="vb-storage-twentysix-demo">
  <div class="vb-storage-twentysix-wrap" data-vb-storage-twentysix>
    <div class="vb-storage-twentysix-head">
      <span class="vb-storage-twentysix-kicker">Example 26</span>
      <h3>Recently Viewed Products</h3>
      <p>Click product cards to add them to the viewed list. Refresh the page and your viewed products stay saved.</p>
    </div>

    <div class="vb-storage-twentysix-layout">
      <div class="vb-storage-twentysix-products">
        <article data-product data-id="hosting" data-name="Cloud Hosting" data-price="29€" data-icon="CH">
          <span>CH</span>
          <h4>Cloud Hosting</h4>
          <p>Fast hosting package for business websites.</p>
          <strong>29€</strong>
          <button type="button" data-view-product>View Product</button>
        </article>

        <article data-product data-id="audit" data-name="SEO Audit" data-price="99€" data-icon="SEO">
          <span>SEO</span>
          <h4>SEO Audit</h4>
          <p>Technical review for rankings and structure.</p>
          <strong>99€</strong>
          <button type="button" data-view-product>View Product</button>
        </article>

        <article data-product data-id="plugin" data-name="Custom Plugin" data-price="399€" data-icon="WP">
          <span>WP</span>
          <h4>Custom Plugin</h4>
          <p>WordPress plugin built for custom workflows.</p>
          <strong>399€</strong>
          <button type="button" data-view-product>View Product</button>
        </article>

        <article data-product data-id="landing" data-name="Landing Page" data-price="249€" data-icon="UI">
          <span>UI</span>
          <h4>Landing Page</h4>
          <p>Modern responsive sales page section.</p>
          <strong>249€</strong>
          <button type="button" data-view-product>View Product</button>
        </article>
      </div>

      <aside class="vb-storage-twentysix-panel">
        <div class="vb-storage-twentysix-panel-head">
          <strong>Viewed Products</strong>
          <button type="button" data-clear-viewed>Clear</button>
        </div>

        <div class="vb-storage-twentysix-list" data-viewed-list>
          <p class="vb-storage-twentysix-empty">No viewed products yet.</p>
        </div>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-storage-twentysix-demo,
.vb-storage-twentysix-demo * {
  box-sizing: border-box;
}

.vb-storage-twentysix-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(236, 72, 153, 0.14), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #fdf2f8 56%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.18);
  box-shadow: 0 28px 80px rgba(88, 28, 135, 0.10);
}

.vb-storage-twentysix-wrap {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-twentysix-head {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-twentysix-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #f3e8ff;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentysix-head h3 {
  max-width: 820px;
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twentysix-head p {
  max-width: 780px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentysix-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 390px);
  gap: 20px;
  align-items: start;
}

.vb-storage-twentysix-products {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.vb-storage-twentysix-products article {
  display: grid;
  align-content: start;
  gap: 11px;
  min-height: 260px;
  padding: 18px;
  border-radius: 26px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-storage-twentysix-products article.is-viewed {
  border-color: rgba(168, 85, 247, 0.55);
  background: linear-gradient(135deg, #faf5ff, #ffffff) !important;
}

.vb-storage-twentysix-products article > span {
  display: grid;
  place-items: center;
  width: 66px;
  height: 66px;
  border-radius: 22px;
  background: linear-gradient(135deg, #a855f7, #ec4899);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 16px;
  font-weight: 950;
}

.vb-storage-twentysix-products h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 23px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
}

.vb-storage-twentysix-products p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-storage-twentysix-products strong {
  margin-top: auto;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 26px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-twentysix-products button {
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #a855f7, #ec4899);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentysix-panel {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 28px;
  background: #111827;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.24);
}

.vb-storage-twentysix-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-twentysix-panel-head strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-twentysix-panel-head button {
  min-height: 38px;
  padding: 0 13px;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 12px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentysix-list {
  display: grid;
  gap: 10px;
  min-height: 320px;
}

.vb-storage-twentysix-empty {
  display: grid;
  place-items: center;
  min-height: 320px;
  margin: 0 !important;
  border: 1px dashed rgba(255,255,255,0.22);
  border-radius: 20px;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-twentysix-viewed {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 12px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-twentysix-viewed span {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 16px;
  background: linear-gradient(135deg, #a855f7, #ec4899);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-twentysix-viewed strong {
  display: block;
  margin-bottom: 4px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.25;
  font-weight: 900;
}

.vb-storage-twentysix-viewed small {
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 12px;
  font-weight: 850;
}

@media (max-width: 940px) {
  .vb-storage-twentysix-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-twentysix-products {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentysix-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This recently viewed products localStorage example is useful for ecommerce stores, WooCommerce layouts, product detail pages, affiliate product roundups, marketplace listings, booking pages, and product recommendation widgets.

27. Product Filters Saved with LocalStorage

Product filters saved with localStorage are useful for ecommerce category pages, real estate listings, job boards, directories, blog archives, product catalogs, and comparison websites. It keeps filter choices active after refresh, so users do not lose their selected view.

This example filters product cards by category, price range, and availability. JavaScript saves the selected filters in localStorage, restores them after refresh, and updates the visible product count instantly.

Product Results 6 products
Website

Landing Page

Modern responsive landing page section.

249€
Plugin

Quote Form Plugin

Custom WordPress quote request system.

399€
SEO

SEO Audit

Technical SEO and content review.

99€
Website

Ecommerce Setup

WooCommerce style product page setup.

499€
Plugin

Dark Mode Widget

Saved theme preference component.

89€
SEO

Content SEO Plan

SEO structure for long-form articles.

299€

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentyseven]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentysevenFilters";
  const filterFields = wrapper.querySelectorAll("[data-filter]");
  const productCards = Array.from(wrapper.querySelectorAll("[data-product-card]"));
  const resultCount = wrapper.querySelector("[data-result-count]");
  const resetButton = wrapper.querySelector("[data-reset-filters]");

  const defaultFilters = {
    category: "all",
    price: "9999",
    available: false
  };

  let filters = Object.assign({}, defaultFilters);

  function saveFilters() {
    localStorage.setItem(storageKey, JSON.stringify(filters));
  }

  function loadFilters() {
    const savedFilters = localStorage.getItem(storageKey);
    if (!savedFilters) return;

    try {
      filters = Object.assign({}, defaultFilters, JSON.parse(savedFilters));
    } catch (error) {
      filters = Object.assign({}, defaultFilters);
      localStorage.removeItem(storageKey);
    }
  }

  function syncFields() {
    filterFields.forEach(function (field) {
      const key = field.getAttribute("data-filter");

      if (field.type === "checkbox") {
        field.checked = Boolean(filters[key]);
      } else {
        field.value = filters[key];
      }
    });
  }

  function applyFilters() {
    let visibleCount = 0;

    productCards.forEach(function (card) {
      const category = card.getAttribute("data-category");
      const price = Number(card.getAttribute("data-price"));
      const available = card.getAttribute("data-available") === "true";

      const matchesCategory = filters.category === "all" || filters.category === category;
      const matchesPrice = price <= Number(filters.price);
      const matchesAvailability = !filters.available || available;
      const isVisible = matchesCategory && matchesPrice && matchesAvailability;

      card.classList.toggle("is-hidden", !isVisible);

      if (isVisible) {
        visibleCount += 1;
      }
    });

    resultCount.textContent = visibleCount === 1 ? "1 product" : visibleCount + " products";
    saveFilters();
  }

  filterFields.forEach(function (field) {
    field.addEventListener("change", function () {
      const key = field.getAttribute("data-filter");
      filters[key] = field.type === "checkbox" ? field.checked : field.value;
      applyFilters();
    });
  });

  resetButton.addEventListener("click", function () {
    filters = Object.assign({}, defaultFilters);
    localStorage.removeItem(storageKey);
    syncFields();
    applyFilters();
  });

  loadFilters();
  syncFields();
  applyFilters();
})();

HTML

<div class="vb-storage-twentyseven-demo">
  <div class="vb-storage-twentyseven-filter" data-vb-storage-twentyseven>
    <aside class="vb-storage-twentyseven-sidebar">
      <span class="vb-storage-twentyseven-kicker">Example 27</span>
      <h3>Saved Product Filters</h3>
      <p>Choose filters and refresh the page. localStorage restores the same product filter state.</p>

      <label>
        <span>Category</span>
        <select data-filter="category">
          <option value="all">All categories</option>
          <option value="website">Website</option>
          <option value="plugin">Plugin</option>
          <option value="seo">SEO</option>
        </select>
      </label>

      <label>
        <span>Max price</span>
        <select data-filter="price">
          <option value="9999">Any price</option>
          <option value="100">Up to 100€</option>
          <option value="300">Up to 300€</option>
          <option value="500">Up to 500€</option>
        </select>
      </label>

      <label class="vb-storage-twentyseven-check">
        <input type="checkbox" data-filter="available">
        <span>Available only</span>
      </label>

      <button type="button" data-reset-filters>Reset Filters</button>
    </aside>

    <section class="vb-storage-twentyseven-results">
      <div class="vb-storage-twentyseven-results-head">
        <strong>Product Results</strong>
        <span data-result-count>6 products</span>
      </div>

      <div class="vb-storage-twentyseven-grid">
        <article data-product-card data-category="website" data-price="249" data-available="true">
          <span>Website</span>
          <h4>Landing Page</h4>
          <p>Modern responsive landing page section.</p>
          <strong>249€</strong>
        </article>

        <article data-product-card data-category="plugin" data-price="399" data-available="true">
          <span>Plugin</span>
          <h4>Quote Form Plugin</h4>
          <p>Custom WordPress quote request system.</p>
          <strong>399€</strong>
        </article>

        <article data-product-card data-category="seo" data-price="99" data-available="true">
          <span>SEO</span>
          <h4>SEO Audit</h4>
          <p>Technical SEO and content review.</p>
          <strong>99€</strong>
        </article>

        <article data-product-card data-category="website" data-price="499" data-available="false">
          <span>Website</span>
          <h4>Ecommerce Setup</h4>
          <p>WooCommerce style product page setup.</p>
          <strong>499€</strong>
        </article>

        <article data-product-card data-category="plugin" data-price="89" data-available="true">
          <span>Plugin</span>
          <h4>Dark Mode Widget</h4>
          <p>Saved theme preference component.</p>
          <strong>89€</strong>
        </article>

        <article data-product-card data-category="seo" data-price="299" data-available="false">
          <span>SEO</span>
          <h4>Content SEO Plan</h4>
          <p>SEO structure for long-form articles.</p>
          <strong>299€</strong>
        </article>
      </div>
    </section>
  </div>
</div>

CSS

.vb-storage-twentyseven-demo,
.vb-storage-twentyseven-demo * {
  box-sizing: border-box;
}

.vb-storage-twentyseven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(37, 99, 235, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(245, 158, 11, 0.15), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #fffbeb 56%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-storage-twentyseven-filter {
  display: grid;
  grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
  gap: 22px;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-twentyseven-sidebar,
.vb-storage-twentyseven-results {
  min-width: 0;
}

.vb-storage-twentyseven-sidebar {
  display: grid;
  align-content: start;
  gap: 13px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 28px;
  background: #0f172a;
}

.vb-storage-twentyseven-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(245, 158, 11, 0.16);
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentyseven-sidebar h3 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 58px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
}

.vb-storage-twentyseven-sidebar p {
  margin: 0 0 8px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-storage-twentyseven-sidebar label {
  display: grid;
  gap: 7px;
}

.vb-storage-twentyseven-sidebar label span {
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-twentyseven-sidebar select {
  width: 100%;
  min-height: 46px;
  padding: 0 13px;
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: 15px;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 750;
  outline: none;
}

.vb-storage-twentyseven-check {
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 10px !important;
  min-height: 46px;
  padding: 10px 12px;
  border-radius: 16px;
  background: rgba(255,255,255,0.08);
}

.vb-storage-twentyseven-check input {
  width: 18px;
  height: 18px;
  accent-color: #f59e0b;
}

.vb-storage-twentyseven-sidebar button {
  min-height: 44px;
  margin-top: 4px;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentyseven-results {
  display: grid;
  gap: 16px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 28px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-twentyseven-results-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.vb-storage-twentyseven-results-head strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-twentyseven-results-head span {
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-twentyseven-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-height: 480px;
}

.vb-storage-twentyseven-grid article {
  display: grid;
  align-content: start;
  gap: 10px;
  min-height: 210px;
  padding: 16px;
  border-radius: 22px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.06);
}

.vb-storage-twentyseven-grid article.is-hidden {
  display: none;
}

.vb-storage-twentyseven-grid article span {
  display: inline-flex;
  justify-self: start;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-storage-twentyseven-grid article h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 21px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
}

.vb-storage-twentyseven-grid article p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-storage-twentyseven-grid article strong {
  margin-top: auto;
  color: #d97706 !important;
  -webkit-text-fill-color: #d97706 !important;
  font-size: 24px;
  line-height: 1;
  font-weight: 950;
}

@media (max-width: 920px) {
  .vb-storage-twentyseven-filter {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-twentyseven-grid {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentyseven-sidebar h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This saved filters localStorage example is useful for ecommerce category pages, WooCommerce product filters, real estate listings, job boards, directories, comparison websites, blog archives, and searchable catalog interfaces.

28. Wishlist Saved with LocalStorage

A wishlist saved with localStorage is useful for ecommerce stores, affiliate websites, product catalogs, marketplaces, course websites, booking platforms, and comparison pages. It lets visitors save favorite items without needing an account or backend database.

This example lets users add products to a wishlist, remove products, clear the wishlist, and keep saved items after refreshing the page. JavaScript stores the wishlist as a JSON array in localStorage and updates the UI instantly.

Example 28

Saved Wishlist

Add items to the wishlist, refresh the page, and localStorage restores your saved favorites.

Website

Starter Website

A clean website package for small businesses and service pages.

299€
Form

Booking Form

A custom booking form with saved form fields and smooth UI.

149€
SEO

SEO Setup

Technical SEO setup for structure, metadata, and internal links.

199€
JavaScript

Custom JS Widget

A lightweight browser feature built with custom JavaScript.

249€

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentyeight]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentyeightWishlist";
  const products = Array.from(wrapper.querySelectorAll("[data-product]"));
  const wishlistList = wrapper.querySelector("[data-wishlist-list]");
  const wishlistCount = wrapper.querySelector("[data-wishlist-count]");
  const clearButton = wrapper.querySelector("[data-clear-wishlist]");

  let wishlist = [];

  function saveWishlist() {
    localStorage.setItem(storageKey, JSON.stringify(wishlist));
  }

  function loadWishlist() {
    const savedWishlist = localStorage.getItem(storageKey);
    if (!savedWishlist) return;

    try {
      const parsedWishlist = JSON.parse(savedWishlist);
      wishlist = Array.isArray(parsedWishlist) ? parsedWishlist : [];
    } catch (error) {
      wishlist = [];
      localStorage.removeItem(storageKey);
    }
  }

  function getProductData(product) {
    return {
      id: product.getAttribute("data-id"),
      name: product.getAttribute("data-name"),
      price: product.getAttribute("data-price"),
      tag: product.getAttribute("data-tag")
    };
  }

  function renderWishlist() {
    wishlistList.innerHTML = "";

    products.forEach(function (product) {
      const productId = product.getAttribute("data-id");
      const isSaved = wishlist.some(function (item) {
        return item.id === productId;
      });

      product.classList.toggle("is-saved", isSaved);
      product.querySelector("[data-wishlist-button]").textContent = isSaved ? "Saved" : "Add to Wishlist";
    });

    wishlistCount.textContent = wishlist.length === 1 ? "1 saved" : wishlist.length + " saved";

    if (wishlist.length === 0) {
      wishlistList.innerHTML = '<p class="vb-storage-twentyeight-empty">No wishlist items saved yet.</p>';
      saveWishlist();
      return;
    }

    wishlist.forEach(function (item) {
      const row = document.createElement("div");
      row.className = "vb-storage-twentyeight-item";

      row.innerHTML =
        '<div>' +
          '<strong>' + item.name + '</strong>' +
          '<span>' + item.tag + ' · ' + item.price + '</span>' +
        '</div>' +
        '<button type="button" class="vb-storage-twentyeight-remove" data-remove-wishlist="' + item.id + '">×</button>';

      wishlistList.appendChild(row);
    });

    saveWishlist();
  }

  function toggleWishlist(product) {
    const productData = getProductData(product);
    const alreadySaved = wishlist.some(function (item) {
      return item.id === productData.id;
    });

    if (alreadySaved) {
      wishlist = wishlist.filter(function (item) {
        return item.id !== productData.id;
      });
    } else {
      wishlist.unshift(productData);
    }

    renderWishlist();
  }

  products.forEach(function (product) {
    product.querySelector("[data-wishlist-button]").addEventListener("click", function () {
      toggleWishlist(product);
    });
  });

  wishlistList.addEventListener("click", function (event) {
    const removeId = event.target.getAttribute("data-remove-wishlist");
    if (!removeId) return;

    wishlist = wishlist.filter(function (item) {
      return item.id !== removeId;
    });

    renderWishlist();
  });

  clearButton.addEventListener("click", function () {
    wishlist = [];
    localStorage.removeItem(storageKey);
    renderWishlist();
  });

  loadWishlist();
  renderWishlist();
})();

HTML

<div class="vb-storage-twentyeight-demo">
  <div class="vb-storage-twentyeight-wishlist" data-vb-storage-twentyeight>
    <div class="vb-storage-twentyeight-head">
      <span class="vb-storage-twentyeight-kicker">Example 28</span>
      <h3>Saved Wishlist</h3>
      <p>Add items to the wishlist, refresh the page, and localStorage restores your saved favorites.</p>
    </div>

    <div class="vb-storage-twentyeight-layout">
      <section class="vb-storage-twentyeight-products">
        <article data-product data-id="starter-site" data-name="Starter Website" data-price="299€" data-tag="Website">
          <span>Website</span>
          <h4>Starter Website</h4>
          <p>A clean website package for small businesses and service pages.</p>
          <strong>299€</strong>
          <button type="button" data-wishlist-button>Add to Wishlist</button>
        </article>

        <article data-product data-id="booking-form" data-name="Booking Form" data-price="149€" data-tag="Form">
          <span>Form</span>
          <h4>Booking Form</h4>
          <p>A custom booking form with saved form fields and smooth UI.</p>
          <strong>149€</strong>
          <button type="button" data-wishlist-button>Add to Wishlist</button>
        </article>

        <article data-product data-id="seo-setup" data-name="SEO Setup" data-price="199€" data-tag="SEO">
          <span>SEO</span>
          <h4>SEO Setup</h4>
          <p>Technical SEO setup for structure, metadata, and internal links.</p>
          <strong>199€</strong>
          <button type="button" data-wishlist-button>Add to Wishlist</button>
        </article>

        <article data-product data-id="custom-widget" data-name="Custom JS Widget" data-price="249€" data-tag="JavaScript">
          <span>JavaScript</span>
          <h4>Custom JS Widget</h4>
          <p>A lightweight browser feature built with custom JavaScript.</p>
          <strong>249€</strong>
          <button type="button" data-wishlist-button>Add to Wishlist</button>
        </article>
      </section>

      <aside class="vb-storage-twentyeight-panel">
        <div class="vb-storage-twentyeight-panel-head">
          <strong>Wishlist</strong>
          <span data-wishlist-count>0 saved</span>
        </div>

        <div class="vb-storage-twentyeight-list" data-wishlist-list>
          <p class="vb-storage-twentyeight-empty">No wishlist items saved yet.</p>
        </div>

        <button type="button" data-clear-wishlist>Clear Wishlist</button>
      </aside>
    </div>
  </div>
</div>

CSS

.vb-storage-twentyeight-demo,
.vb-storage-twentyeight-demo * {
  box-sizing: border-box;
}

.vb-storage-twentyeight-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(244, 63, 94, 0.16), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(168, 85, 247, 0.15), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #faf5ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(244, 63, 94, 0.18);
  box-shadow: 0 28px 80px rgba(159, 18, 57, 0.10);
}

.vb-storage-twentyeight-wishlist {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-twentyeight-head {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.vb-storage-twentyeight-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentyeight-head h3 {
  max-width: 820px;
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twentyeight-head p {
  max-width: 780px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentyeight-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 390px);
  gap: 20px;
  align-items: start;
}

.vb-storage-twentyeight-products {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.vb-storage-twentyeight-products article {
  display: grid;
  align-content: start;
  gap: 10px;
  min-height: 250px;
  padding: 18px;
  border-radius: 26px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-storage-twentyeight-products article.is-saved {
  border-color: rgba(244, 63, 94, 0.56);
  background: linear-gradient(135deg, #fff1f2, #ffffff) !important;
}

.vb-storage-twentyeight-products article span {
  display: inline-flex;
  justify-self: start;
  padding: 7px 10px;
  border-radius: 999px;
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-storage-twentyeight-products h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 23px !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
}

.vb-storage-twentyeight-products p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-storage-twentyeight-products strong {
  margin-top: auto;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 26px;
  line-height: 1;
  font-weight: 950;
}

.vb-storage-twentyeight-products button,
.vb-storage-twentyeight-panel > button,
.vb-storage-twentyeight-remove {
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentyeight-products button {
  background: linear-gradient(135deg, #f43f5e, #a855f7);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-twentyeight-products article.is-saved button {
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
}

.vb-storage-twentyeight-panel {
  display: grid;
  gap: 14px;
  padding: 18px;
  border-radius: 28px;
  background: #111827;
  box-shadow: 0 22px 64px rgba(15, 23, 42, 0.24);
}

.vb-storage-twentyeight-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.vb-storage-twentyeight-panel-head strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  font-weight: 950;
}

.vb-storage-twentyeight-panel-head span {
  color: #fecdd3 !important;
  -webkit-text-fill-color: #fecdd3 !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-twentyeight-list {
  display: grid;
  gap: 10px;
  min-height: 320px;
}

.vb-storage-twentyeight-empty {
  display: grid;
  place-items: center;
  min-height: 320px;
  margin: 0 !important;
  border: 1px dashed rgba(255,255,255,0.22);
  border-radius: 20px;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-twentyeight-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 12px;
  border-radius: 18px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-twentyeight-item strong {
  display: block;
  margin-bottom: 4px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  line-height: 1.25;
  font-weight: 900;
}

.vb-storage-twentyeight-item span {
  color: #fecdd3 !important;
  -webkit-text-fill-color: #fecdd3 !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-storage-twentyeight-remove {
  width: 34px;
  height: 34px;
  min-height: 34px;
  background: #ffe4e6;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: 16px;
}

.vb-storage-twentyeight-panel > button {
  background: #ffffff;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
}

@media (max-width: 940px) {
  .vb-storage-twentyeight-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-storage-twentyeight-products {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentyeight-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This wishlist localStorage example is useful for ecommerce stores, affiliate product lists, marketplace cards, booking platforms, course catalogs, digital product shops, and WooCommerce-style favorite product features.

29. Multi-Step Form Progress Saved with LocalStorage

Saving multi-step form progress with localStorage is useful for quote request forms, checkout flows, onboarding forms, application forms, booking forms, surveys, and lead generation funnels. It prevents users from losing progress when they refresh or return later.

This example saves the current form step and entered field values. JavaScript restores the same step after refresh, keeps the typed data, updates the progress bar, and lets users reset the saved progress.

Example 29

Saved Multi-Step Form

Fill the steps, refresh the page, and localStorage restores your current step and field values.

Step 1 of 3

Contact Details

Project Type

Project Message

Your saved form summary will appear here.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-twentynine]");
  if (!wrapper) return;

  const storageKey = "vbStorageTwentynineMultiStep";
  const form = wrapper.querySelector("[data-multistep-form]");
  const panels = Array.from(wrapper.querySelectorAll("[data-step-panel]"));
  const fields = Array.from(form.querySelectorAll("input, select, textarea"));
  const prevButton = wrapper.querySelector("[data-prev-step]");
  const nextButton = wrapper.querySelector("[data-next-step]");
  const resetButton = wrapper.querySelector("[data-reset-progress]");
  const stepLabel = wrapper.querySelector("[data-step-label]");
  const stepProgress = wrapper.querySelector("[data-step-progress]");
  const summary = wrapper.querySelector("[data-form-summary]");

  let state = {
    step: 0,
    data: {}
  };

  function saveState() {
    localStorage.setItem(storageKey, JSON.stringify(state));
  }

  function loadState() {
    const savedState = localStorage.getItem(storageKey);
    if (!savedState) return;

    try {
      const parsedState = JSON.parse(savedState);
      state.step = Number(parsedState.step) || 0;
      state.data = parsedState.data || {};
    } catch (error) {
      localStorage.removeItem(storageKey);
    }
  }

  function collectData() {
    fields.forEach(function (field) {
      state.data[field.name] = field.value;
    });
  }

  function applyData() {
    fields.forEach(function (field) {
      field.value = state.data[field.name] || "";
    });
  }

  function updateSummary() {
    summary.textContent =
      "Name: " + (state.data.name || "Not added") +
      " · Project: " + (state.data.project || "Not selected") +
      " · Budget: " + (state.data.budget || "Not selected");
  }

  function renderStep() {
    panels.forEach(function (panel, index) {
      panel.classList.toggle("is-active", index === state.step);
    });

    stepLabel.textContent = "Step " + (state.step + 1) + " of " + panels.length;
    stepProgress.style.width = ((state.step + 1) / panels.length * 100) + "%";

    prevButton.disabled = state.step === 0;
    nextButton.textContent = state.step === panels.length - 1 ? "Finish Demo" : "Next";

    updateSummary();
    saveState();
  }

  fields.forEach(function (field) {
    field.addEventListener("input", function () {
      collectData();
      updateSummary();
      saveState();
    });

    field.addEventListener("change", function () {
      collectData();
      updateSummary();
      saveState();
    });
  });

  prevButton.addEventListener("click", function () {
    collectData();
    state.step = Math.max(0, state.step - 1);
    renderStep();
  });

  nextButton.addEventListener("click", function () {
    collectData();

    if (state.step < panels.length - 1) {
      state.step += 1;
      renderStep();
      return;
    }

    summary.textContent = "Demo finished. Your progress is still saved until you reset it.";
    saveState();
  });

  resetButton.addEventListener("click", function () {
    state = {
      step: 0,
      data: {}
    };

    localStorage.removeItem(storageKey);
    form.reset();
    renderStep();
  });

  loadState();
  applyData();
  renderStep();
})();

HTML

<div class="vb-storage-twentynine-demo">
  <div class="vb-storage-twentynine-formapp" data-vb-storage-twentynine>
    <div class="vb-storage-twentynine-intro">
      <span class="vb-storage-twentynine-kicker">Example 29</span>
      <h3>Saved Multi-Step Form</h3>
      <p>Fill the steps, refresh the page, and localStorage restores your current step and field values.</p>

      <div class="vb-storage-twentynine-progress">
        <span data-step-label>Step 1 of 3</span>
        <div><i data-step-progress></i></div>
      </div>
    </div>

    <form class="vb-storage-twentynine-form" data-multistep-form>
      <section data-step-panel="0" class="is-active">
        <h4>Contact Details</h4>
        <label>
          <span>Name</span>
          <input type="text" name="name" placeholder="Your name">
        </label>
        <label>
          <span>Email</span>
          <input type="email" name="email" placeholder="you@example.com">
        </label>
      </section>

      <section data-step-panel="1">
        <h4>Project Type</h4>
        <label>
          <span>What do you need?</span>
          <select name="project">
            <option value="">Choose project</option>
            <option value="Website">Website</option>
            <option value="Ecommerce">Ecommerce</option>
            <option value="WordPress Plugin">WordPress Plugin</option>
            <option value="JavaScript Feature">JavaScript Feature</option>
          </select>
        </label>
        <label>
          <span>Budget</span>
          <select name="budget">
            <option value="">Choose budget</option>
            <option value="Under 500€">Under 500€</option>
            <option value="500€ - 1500€">500€ - 1500€</option>
            <option value="1500€+">1500€+</option>
          </select>
        </label>
      </section>

      <section data-step-panel="2">
        <h4>Project Message</h4>
        <label>
          <span>Message</span>
          <textarea name="message" rows="7" placeholder="Describe your project..."></textarea>
        </label>
        <div class="vb-storage-twentynine-summary" data-form-summary>
          Your saved form summary will appear here.
        </div>
      </section>

      <div class="vb-storage-twentynine-actions">
        <button type="button" data-prev-step>Back</button>
        <button type="button" data-next-step>Next</button>
        <button type="button" data-reset-progress>Reset</button>
      </div>
    </form>
  </div>
</div>

CSS

.vb-storage-twentynine-demo,
.vb-storage-twentynine-demo * {
  box-sizing: border-box;
}

.vb-storage-twentynine-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(14, 165, 233, 0.17), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #eef2ff 56%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  box-shadow: 0 28px 80px rgba(12, 74, 110, 0.10);
}

.vb-storage-twentynine-formapp {
  display: grid;
  grid-template-columns: minmax(0, 0.88fr) minmax(0, 1.12fr);
  gap: 22px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-storage-twentynine-intro,
.vb-storage-twentynine-form {
  min-width: 0;
}

.vb-storage-twentynine-intro {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 28px;
  background: #0f172a;
}

.vb-storage-twentynine-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(14, 165, 233, 0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-twentynine-intro h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-twentynine-intro p {
  max-width: 560px;
  margin: 0 0 22px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-twentynine-progress {
  display: grid;
  gap: 9px;
  padding: 15px;
  border-radius: 20px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-storage-twentynine-progress span {
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 13px;
  font-weight: 900;
}

.vb-storage-twentynine-progress div {
  height: 11px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
}

.vb-storage-twentynine-progress i {
  display: block;
  width: 33.33%;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(135deg, #0ea5e9, #6366f1);
  transition: width 0.25s ease;
}

.vb-storage-twentynine-form {
  display: grid;
  gap: 16px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-storage-twentynine-form section {
  display: none;
  min-height: 340px;
  align-content: start;
  gap: 14px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 24px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.06);
}

.vb-storage-twentynine-form section.is-active {
  display: grid;
}

.vb-storage-twentynine-form h4 {
  margin: 0 0 4px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(28px, 4vw, 42px) !important;
  line-height: 1.05 !important;
  font-weight: 950 !important;
  letter-spacing: -0.055em;
}

.vb-storage-twentynine-form label {
  display: grid;
  gap: 7px;
}

.vb-storage-twentynine-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-twentynine-form input,
.vb-storage-twentynine-form select,
.vb-storage-twentynine-form textarea {
  width: 100%;
  border: 1px solid rgba(148, 163, 184, 0.34);
  border-radius: 16px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 700;
  outline: none;
}

.vb-storage-twentynine-form input,
.vb-storage-twentynine-form select {
  min-height: 50px;
  padding: 0 15px;
}

.vb-storage-twentynine-form textarea {
  resize: vertical;
  padding: 14px 15px;
  line-height: 1.55;
}

.vb-storage-twentynine-form input:focus,
.vb-storage-twentynine-form select:focus,
.vb-storage-twentynine-form textarea:focus {
  border-color: rgba(14, 165, 233, 0.72);
  box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.10);
}

.vb-storage-twentynine-summary {
  padding: 14px;
  border-radius: 18px;
  background: #eff6ff;
  color: #1e3a8a !important;
  -webkit-text-fill-color: #1e3a8a !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 750;
}

.vb-storage-twentynine-actions {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 10px;
}

.vb-storage-twentynine-actions button {
  min-height: 46px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-twentynine-actions button:first-child {
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
}

.vb-storage-twentynine-actions button:nth-child(2) {
  background: linear-gradient(135deg, #0ea5e9, #6366f1);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-twentynine-actions button:last-child {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-storage-twentynine-actions button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

@media (max-width: 900px) {
  .vb-storage-twentynine-formapp {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 580px) {
  .vb-storage-twentynine-actions {
    grid-template-columns: 1fr;
  }

  .vb-storage-twentynine-intro h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This multi-step form progress localStorage example is useful for quote request forms, onboarding flows, checkout forms, booking forms, application forms, surveys, lead generation funnels, and long contact forms.

30. LocalStorage CRUD App

A localStorage CRUD app is one of the best JavaScript localStorage examples because it combines the most important browser storage patterns: create, read, update, and delete. It is useful for task managers, admin demos, dashboards, small browser apps, saved lists, and JavaScript portfolio projects.

This example lets users create records, edit existing records, delete records, search saved records, and restore everything after refresh. JavaScript stores the records as a JSON array in localStorage and rebuilds the interface whenever the data changes.

Example 30

LocalStorage CRUD App

Create, edit, delete, search, and restore saved records using localStorage.

Add New Record

0 records saved

No records saved yet.

JavaScript

(function () {
  const wrapper = document.querySelector("[data-vb-storage-thirty]");
  if (!wrapper) return;

  const storageKey = "vbStorageThirtyCrudRecords";
  const form = wrapper.querySelector("[data-crud-form]");
  const formTitle = wrapper.querySelector("[data-form-title]");
  const titleInput = wrapper.querySelector("[data-record-title]");
  const categoryInput = wrapper.querySelector("[data-record-category]");
  const descriptionInput = wrapper.querySelector("[data-record-description]");
  const saveButton = wrapper.querySelector("[data-save-record]");
  const cancelButton = wrapper.querySelector("[data-cancel-edit]");
  const searchInput = wrapper.querySelector("[data-record-search]");
  const clearButton = wrapper.querySelector("[data-clear-records]");
  const recordCount = wrapper.querySelector("[data-record-count]");
  const recordList = wrapper.querySelector("[data-record-list]");

  let records = [];
  let editingId = null;

  function saveRecords() {
    localStorage.setItem(storageKey, JSON.stringify(records));
  }

  function loadRecords() {
    const savedRecords = localStorage.getItem(storageKey);
    if (!savedRecords) return;

    try {
      const parsedRecords = JSON.parse(savedRecords);
      records = Array.isArray(parsedRecords) ? parsedRecords : [];
    } catch (error) {
      records = [];
      localStorage.removeItem(storageKey);
    }
  }

  function resetForm() {
    editingId = null;
    form.reset();
    formTitle.textContent = "Add New Record";
    saveButton.textContent = "Save Record";
    cancelButton.hidden = true;
  }

  function getFilteredRecords() {
    const search = searchInput.value.trim().toLowerCase();

    if (!search) {
      return records;
    }

    return records.filter(function (record) {
      return (
        record.title.toLowerCase().includes(search) ||
        record.category.toLowerCase().includes(search) ||
        record.description.toLowerCase().includes(search)
      );
    });
  }

  function renderRecords() {
    const filteredRecords = getFilteredRecords();
    recordList.innerHTML = "";

    recordCount.textContent = records.length === 1
      ? "1 record saved"
      : records.length + " records saved";

    if (filteredRecords.length === 0) {
      recordList.innerHTML = '<p class="vb-storage-thirty-empty">No records found.</p>';
      return;
    }

    filteredRecords.forEach(function (record) {
      const card = document.createElement("article");
      card.className = "vb-storage-thirty-record";

      card.innerHTML =
        '<div class="vb-storage-thirty-record-top">' +
          '<h5>' + record.title + '</h5>' +
          '<span>' + record.category + '</span>' +
        '</div>' +
        '<p>' + record.description + '</p>' +
        '<div class="vb-storage-thirty-actions">' +
          '<button type="button" data-edit-record="' + record.id + '">Edit</button>' +
          '<button type="button" data-delete-record="' + record.id + '">Delete</button>' +
        '</div>';

      recordList.appendChild(card);
    });
  }

  function saveRecord() {
    const title = titleInput.value.trim();
    const category = categoryInput.value;
    const description = descriptionInput.value.trim();

    if (!title || !description) return;

    if (editingId) {
      records = records.map(function (record) {
        if (record.id === editingId) {
          return {
            id: record.id,
            title: title,
            category: category,
            description: description
          };
        }

        return record;
      });
    } else {
      records.unshift({
        id: String(Date.now()),
        title: title,
        category: category,
        description: description
      });
    }

    saveRecords();
    renderRecords();
    resetForm();
  }

  function editRecord(recordId) {
    const record = records.find(function (item) {
      return item.id === recordId;
    });

    if (!record) return;

    editingId = record.id;
    titleInput.value = record.title;
    categoryInput.value = record.category;
    descriptionInput.value = record.description;
    formTitle.textContent = "Edit Record";
    saveButton.textContent = "Update Record";
    cancelButton.hidden = false;
    titleInput.focus();
  }

  function deleteRecord(recordId) {
    records = records.filter(function (record) {
      return record.id !== recordId;
    });

    if (editingId === recordId) {
      resetForm();
    }

    saveRecords();
    renderRecords();
  }

  form.addEventListener("submit", function (event) {
    event.preventDefault();
    saveRecord();
  });

  recordList.addEventListener("click", function (event) {
    const editId = event.target.getAttribute("data-edit-record");
    const deleteId = event.target.getAttribute("data-delete-record");

    if (editId) editRecord(editId);
    if (deleteId) deleteRecord(deleteId);
  });

  searchInput.addEventListener("input", renderRecords);

  cancelButton.addEventListener("click", resetForm);

  clearButton.addEventListener("click", function () {
    records = [];
    localStorage.removeItem(storageKey);
    resetForm();
    renderRecords();
  });

  loadRecords();
  renderRecords();
})();

HTML

<div class="vb-storage-thirty-demo">
  <div class="vb-storage-thirty-app" data-vb-storage-thirty>
    <div class="vb-storage-thirty-head">
      <span class="vb-storage-thirty-kicker">Example 30</span>
      <h3>LocalStorage CRUD App</h3>
      <p>Create, edit, delete, search, and restore saved records using localStorage.</p>
    </div>

    <div class="vb-storage-thirty-layout">
      <form class="vb-storage-thirty-form" data-crud-form>
        <h4 data-form-title>Add New Record</h4>

        <label>
          <span>Title</span>
          <input type="text" data-record-title placeholder="Project title">
        </label>

        <label>
          <span>Category</span>
          <select data-record-category>
            <option value="Website">Website</option>
            <option value="JavaScript">JavaScript</option>
            <option value="SEO">SEO</option>
            <option value="WordPress">WordPress</option>
          </select>
        </label>

        <label>
          <span>Description</span>
          <textarea data-record-description rows="5" placeholder="Short description..."></textarea>
        </label>

        <button type="submit" data-save-record>Save Record</button>
        <button type="button" class="vb-storage-thirty-cancel" data-cancel-edit hidden>Cancel Edit</button>
      </form>

      <section class="vb-storage-thirty-records">
        <div class="vb-storage-thirty-toolbar">
          <input type="search" data-record-search placeholder="Search saved records...">
          <button type="button" data-clear-records>Clear All</button>
        </div>

        <div class="vb-storage-thirty-count" data-record-count>0 records saved</div>

        <div class="vb-storage-thirty-list" data-record-list>
          <p class="vb-storage-thirty-empty">No records saved yet.</p>
        </div>
      </section>
    </div>
  </div>
</div>

CSS

.vb-storage-thirty-demo,
.vb-storage-thirty-demo * {
  box-sizing: border-box;
}

.vb-storage-thirty-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 44px;
  background:
    radial-gradient(circle at 14% 18%, rgba(37, 99, 235, 0.17), transparent 34%),
    radial-gradient(circle at 86% 16%, rgba(20, 184, 166, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #f0fdfa 56%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.18);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-storage-thirty-app {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 34px;
  background: #0f172a;
  box-shadow: 0 28px 90px rgba(15, 23, 42, 0.30);
}

.vb-storage-thirty-head {
  display: grid;
  gap: 12px;
  margin-bottom: 22px;
}

.vb-storage-thirty-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(45, 212, 191, 0.16);
  color: #99f6e4 !important;
  -webkit-text-fill-color: #99f6e4 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-storage-thirty-head h3 {
  max-width: 850px;
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 66px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
}

.vb-storage-thirty-head p {
  max-width: 760px;
  margin: 0 !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-storage-thirty-layout {
  display: grid;
  grid-template-columns: minmax(300px, 390px) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}

.vb-storage-thirty-form,
.vb-storage-thirty-records {
  min-width: 0;
}

.vb-storage-thirty-form {
  display: grid;
  gap: 13px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 28px;
  background: #ffffff;
}

.vb-storage-thirty-form h4 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 28px !important;
  line-height: 1.08 !important;
  font-weight: 950 !important;
  letter-spacing: -0.05em;
}

.vb-storage-thirty-form label {
  display: grid;
  gap: 7px;
}

.vb-storage-thirty-form label span {
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-storage-thirty-form input,
.vb-storage-thirty-form select,
.vb-storage-thirty-form textarea,
.vb-storage-thirty-toolbar input {
  width: 100%;
  border: 1px solid rgba(148, 163, 184, 0.34);
  border-radius: 16px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 700;
  outline: none;
}

.vb-storage-thirty-form input,
.vb-storage-thirty-form select,
.vb-storage-thirty-toolbar input {
  min-height: 50px;
  padding: 0 15px;
}

.vb-storage-thirty-form textarea {
  resize: vertical;
  padding: 14px 15px;
  line-height: 1.55;
}

.vb-storage-thirty-form input:focus,
.vb-storage-thirty-form select:focus,
.vb-storage-thirty-form textarea:focus,
.vb-storage-thirty-toolbar input:focus {
  border-color: rgba(20, 184, 166, 0.72);
  box-shadow: 0 0 0 4px rgba(20, 184, 166, 0.10);
}

.vb-storage-thirty-form button,
.vb-storage-thirty-toolbar button,
.vb-storage-thirty-actions button {
  min-height: 44px;
  border: 0;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-storage-thirty-form button[type="submit"] {
  background: linear-gradient(135deg, #2563eb, #14b8a6);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-storage-thirty-cancel,
.vb-storage-thirty-toolbar button,
.vb-storage-thirty-actions button:last-child {
  background: #fee2e2 !important;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-storage-thirty-records {
  display: grid;
  gap: 12px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 28px;
  background: #ffffff;
}

.vb-storage-thirty-toolbar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
}

.vb-storage-thirty-toolbar button {
  padding: 0 14px;
}

.vb-storage-thirty-count {
  padding: 12px 14px;
  border-radius: 18px;
  background: #eff6ff;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-storage-thirty-list {
  display: grid;
  gap: 10px;
  min-height: 430px;
}

.vb-storage-thirty-empty {
  display: grid;
  place-items: center;
  min-height: 430px;
  margin: 0 !important;
  border: 1px dashed rgba(148, 163, 184, 0.55);
  border-radius: 20px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-storage-thirty-record {
  display: grid;
  gap: 10px;
  padding: 14px;
  border-radius: 20px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 10px 26px rgba(15, 23, 42, 0.06);
}

.vb-storage-thirty-record-top {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: start;
}

.vb-storage-thirty-record h5 {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 19px !important;
  line-height: 1.15 !important;
  font-weight: 950 !important;
}

.vb-storage-thirty-record span {
  flex: 0 0 auto;
  padding: 7px 10px;
  border-radius: 999px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 11px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-storage-thirty-record p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-storage-thirty-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.vb-storage-thirty-actions button {
  min-height: 36px;
  padding: 0 12px;
}

.vb-storage-thirty-actions button:first-child {
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
}

@media (max-width: 940px) {
  .vb-storage-thirty-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 620px) {
  .vb-storage-thirty-toolbar {
    grid-template-columns: 1fr;
  }

  .vb-storage-thirty-head h3 {
    font-size: 38px !important;
    letter-spacing: -0.055em;
  }
}

This localStorage CRUD app example is useful for task managers, saved lists, admin demos, browser-based apps, JavaScript portfolio projects, lightweight dashboards, and learning how to create, read, update, and delete data in localStorage.

EntitySchema Pro WordPress structured data and entity SEO plugin
WordPress SEO Plugin

Build AI-readable structured data with EntitySchema Pro

Improve your WordPress SEO with entity-based schema markup, structured content signals, and modern AI-readable website data.

29€ Regular price 89€ Save 60€
View Plugin

JavaScript LocalStorage Best Practices

JavaScript localStorage is simple to use, but the best localStorage features are planned carefully. A good implementation should store only useful data, keep the interface in sync with the saved value, handle invalid stored data safely, and give users a clear way to remove or reset saved information.

For SEO-focused websites, ecommerce pages, WordPress landing pages, SaaS dashboards, and interactive blog examples, localStorage works best when it improves usability without hiding important content from search engines. It should remember user actions, not replace meaningful HTML content that should be visible on the page.

01 Use clear storage keys

Use unique localStorage keys for every feature, plugin, component, or demo so one saved value does not overwrite another.

02 Store non-sensitive data only

Use localStorage for preferences, carts, drafts, filters, and UI state. Do not store passwords, private tokens, payment data, or confidential information.

03 Use JSON for complex data

Arrays and objects must be saved with JSON.stringify() and restored with JSON.parse(), because localStorage stores values as strings.

04 Always update the UI after storage changes

When data is added, edited, removed, or reset, update both localStorage and the visible interface at the same time.

When localStorage is used correctly, it can make small website features feel much more polished. A saved form draft, persistent cart, remembered dark mode setting, or restored filter state can reduce friction and make a website feel more useful without requiring a login system or database for every small interaction.

Responsive LocalStorage UI Tips

Many localStorage examples involve forms, product cards, dashboards, carts, lists, filters, tabs, and settings panels. These components often look fine on desktop but become hard to use on mobile if spacing, button size, scroll behavior, and layout changes are not planned.

A responsive localStorage UI should make saved data easy to understand on every screen size. Users should be able to add, remove, reset, edit, or restore saved items just as easily on a phone as they can on a desktop screen.

Mobile-first controls

Use full-width buttons, readable labels, and enough spacing between action buttons such as save, remove, reset, next, back, and clear.

Readable saved states

Saved status messages, restored draft notices, cart totals, and active filter labels should stay visible and easy to scan on small screens.

Flexible layouts

Use CSS grid or flexbox to move sidebars below content, stack product cards, and keep settings panels usable on mobile devices.

Responsive design is especially important for localStorage features because saved data often grows over time. A cart can get more items, a notes app can get longer text, a recent search list can expand, and a CRUD app can contain many records. The layout should stay usable even when the saved browser data is not empty.

Common JavaScript LocalStorage Mistakes

LocalStorage is easy to start with, but there are common mistakes that can make a browser-based feature unreliable, insecure, or frustrating. Most problems happen when developers store the wrong data, forget to parse JSON safely, do not update the UI after changes, or use the same storage key in multiple places.

Storing sensitive information

Passwords, private tokens, payment details, and confidential customer data should never be stored in localStorage.

Forgetting JSON.parse() error handling

Stored JSON can become invalid. Use try/catch before using parsed browser data in your interface.

Using the same storage key everywhere

Repeated keys can overwrite carts, forms, filters, or demo data. Every component should use its own unique key.

Not giving users a reset option

If a website remembers data, users should have a simple way to clear it, remove items, or reset the saved state.

The safest approach is to treat localStorage as a browser-based helper for user experience. It is excellent for saved interface state, but it should not be treated like a protected backend system. When the data is important, private, shared between devices, or connected to payments and accounts, use server-side storage instead.

JavaScript LocalStorage FAQ

Here are common questions about JavaScript localStorage examples, browser storage, saved form data, shopping carts, dark mode preferences, saved filters, recently viewed products, CRUD apps, and responsive UI components.

JavaScript localStorage is a browser storage feature that lets websites save small pieces of data on the visitor’s device. The data can remain available after refresh, after closing the tab, and after returning to the page later.

LocalStorage can be used for form drafts, shopping carts, dark mode preferences, saved filters, recent searches, recently viewed products, wishlists, notes, dashboard settings, quiz progress, tab state, accordion state, and small browser-based tools.

No. Passwords, private tokens, payment details, personal identity data, and confidential information should not be stored in localStorage. Use secure server-side storage and proper authentication systems for sensitive data.

Objects and arrays should be converted to strings with JSON.stringify() before saving. When reading the value back, use JSON.parse() to turn the string back into an object or array. It is best to wrap JSON.parse() in try/catch.

Yes. That is one of the main reasons localStorage is useful. It can keep data after a page refresh, so features like saved form drafts, carts, filters, and theme preferences can be restored when the page loads again.

Use localStorage for small, non-sensitive browser features that only need to exist on one device. Use a database when the data must be secure, shared between devices, connected to user accounts, used by other visitors, or stored permanently on the server.

Conclusion

JavaScript localStorage is one of the most practical browser features for building interactive website components that remember user actions. It can improve contact forms, quote request forms, ecommerce carts, dark mode toggles, saved filters, wishlists, recently viewed products, dashboard settings, CRUD apps, notes, quizzes, calculators, onboarding flows, and many other front-end UI patterns.

The best localStorage examples do more than save a value. They restore the interface after refresh, update the UI when data changes, use clear storage keys, support reset actions, handle JSON safely, and avoid storing sensitive information. That combination makes localStorage useful for real projects instead of only simple tutorials.

You can use the examples in this guide as starting points for WordPress pages, ecommerce websites, SaaS dashboards, landing pages, portfolios, learning projects, admin panels, and browser-based JavaScript tools. Customize the storage keys, layout, copy, colors, form fields, product data, and reset logic to match your own website or client project.

For production websites, remember the most important rule: localStorage is excellent for non-sensitive browser data, but secure data belongs on the server. Use it for user experience improvements, saved UI state, and practical front-end features — not for passwords, tokens, payments, or confidential customer details.

Continue building better website sections and interactive UI components with these related JavaScript and CSS guides. These posts work well together when building full landing pages, product pages, dashboards, ecommerce interfaces, forms, filters, menus, and responsive website layouts.

JavaScript countdown timers are useful interactive UI components for sale pages, ecommerce campaigns, product launches, event landing pages, webinar registrations, coming soon pages, limited-time offers, checkout urgency blocks, and dashboard widgets. They help show how much time is left before an offer ends, an event starts, a launch goes live, or an important deadline expires.

In this guide, you will find 30 JavaScript countdown timer examples for real website projects, including sale countdown timers, event countdown timers, product launch timers, webinar timers, daily deal timers, checkout timers, coming soon timers, circular countdowns, flip clock timers, progress countdowns, card-based timers, sticky countdown bars, dashboard timers, responsive countdown sections, and reusable vanilla JavaScript timer components.

This post focuses on JavaScript countdown timer logic, date calculations, time remaining updates, days, hours, minutes and seconds formatting, interval handling, expired states, responsive timer layouts, sale timer UI design, and copy-paste-ready HTML, CSS, and JavaScript examples. For related UI components, you can also explore our JavaScript accordion examples, JavaScript search filter examples, JavaScript dropdown menu examples, and JavaScript slider examples.

View All 30 JavaScript Countdown Timer Examples

What Is a JavaScript Countdown Timer?

A JavaScript countdown timer is an interactive component that calculates the time remaining until a specific date, time, offer deadline, product launch, event start, webinar, sale end, or campaign expiry. It usually displays days, hours, minutes, and seconds, then updates automatically while the visitor stays on the page.

The main purpose of a countdown timer is to make time-sensitive information clear. Instead of writing “Sale ends soon” or “Event starts next week,” a countdown timer shows the exact remaining time in a visual format that is easy to understand at a glance.

A JavaScript countdown timer can be very simple or highly advanced. A basic version may count down to one fixed date. A more advanced version may include recurring daily timers, limited-time offers, timezone-aware deadlines, animated digits, flip clock effects, circular progress, expired states, reset logic, local storage, dynamic labels, or multiple timers on the same page.

Why Countdown Timers Matter

Countdown timers matter because many website visitors need clear timing information before they take action. If a sale, registration, product launch, event, limited offer, or booking window has a deadline, a countdown timer can make that deadline more visible and easier to understand.

Countdown timers should be used carefully. They work best when the deadline is real and useful. A timer should support the page message, not feel fake or manipulative. A good JavaScript countdown timer gives visitors clear information, improves the visual hierarchy, and makes the page easier to understand.

JavaScript Countdown Timer Types

There are many different types of JavaScript countdown timers. Some are simple text-based timers, while others are designed as full sale banners, checkout urgency blocks, event hero sections, product launch cards, dashboard widgets, or animated visual components.

The right countdown timer depends on the goal of the page. A sale timer should be highly visible and conversion-focused. An event timer should clearly show the event date and registration action. A dashboard timer may be compact and functional. A coming soon timer can be more visual, atmospheric, and brand-focused.

This guide focuses on JavaScript countdown timer examples, so every demo will include visible JavaScript, HTML, and CSS code. The examples are designed to be copy-paste friendly, easy to customize, and different in layout, purpose, visual style, timer logic, and responsive behavior.

What Should a Good Countdown Timer Include?

A good JavaScript countdown timer should be accurate, readable, responsive, and visually connected to the page goal. Users should quickly understand what the timer is counting down to, how much time is left, and what action they can take before the deadline ends.

Clear deadline context

The timer should explain what happens when the countdown reaches zero, such as a sale ending, event starting, launch opening, or offer expiring.

Readable time units

Days, hours, minutes, and seconds should be easy to scan, with labels that stay readable on desktop, tablet, and mobile screens.

Automatic updates

JavaScript should update the remaining time regularly and handle expired states when the countdown reaches zero.

Strong visual hierarchy

The countdown should stand out enough to support the campaign, but it should not make the page feel messy or hard to read.

Before building a countdown timer, decide what the timer should do after it reaches zero. Should it show an expired message, hide the offer, disable a button, change the call-to-action, reset automatically, or show a new deadline? This behavior is important because a countdown timer is not only a visual element — it is part of the page logic.

You can combine JavaScript countdown timers with many other website UI patterns. Sale timers work well with modern CSS pricing tables, event timers can be placed inside modern website hero sections, checkout urgency timers can pair with modern CSS forms, and campaign sections can use layouts from our modern CSS layouts guide.

30 JavaScript Countdown Timer Examples

Now let’s look at 30 JavaScript countdown timer examples for real website projects. Each example uses a different timer layout, visual style, countdown purpose, sale campaign design, event UI, product launch pattern, checkout urgency block, dashboard widget, animated number style, expired state, or responsive countdown approach, so you can build professional time-based UI components with visible JavaScript, HTML, and CSS code.

1. Flash Sale Countdown Timer

A flash sale countdown timer is useful for ecommerce campaigns, landing pages, seasonal discounts, product promotions, and limited-time offers. It gives visitors a clear visual deadline and helps highlight when a sale period is ending.

This example uses a bold ecommerce sale banner with a strong discount badge, large countdown cards, campaign text, and a clear call-to-action button. The JavaScript uses a rolling weekly campaign cycle, so the demo keeps working in the future without showing an expired old date.

Example 01

Flash Sale Countdown Timer

Show the remaining time for a weekly flash sale, limited product offer, seasonal campaign, or ecommerce promotion.

40% OFF Limited-time ecommerce campaign
Flash Sale Sale active
00 Days
00 Hours
00 Minutes
00 Seconds

Offer ends soon. Grab the deal before the timer reaches zero.

Shop the Sale

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-one]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 7;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-01T00:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");
  const cycleLabelEl = timer.querySelector("[data-cycle-label]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextCycleEnd(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextCycleEnd = getNextCycleEnd(now);
    let remaining = nextCycleEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);

    cycleLabelEl.textContent = "Sale ends this cycle";
    statusEl.textContent = "Offer ends soon. Grab the deal before the timer reaches zero.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-one-demo">
  <div class="vb-countdown-one-sale" data-vb-countdown-one data-cycle-days="7">
    <div class="vb-countdown-one-copy">
      <span class="vb-countdown-one-kicker">Example 01</span>
      <h3>Flash Sale Countdown Timer</h3>
      <p>Show the remaining time for a weekly flash sale, limited product offer, seasonal campaign, or ecommerce promotion.</p>

      <div class="vb-countdown-one-offer">
        <strong>40% OFF</strong>
        <span>Limited-time ecommerce campaign</span>
      </div>
    </div>

    <div class="vb-countdown-one-panel">
      <div class="vb-countdown-one-panel-head">
        <span>Flash Sale</span>
        <strong data-cycle-label>Sale active</strong>
      </div>

      <div class="vb-countdown-one-timer" aria-label="Flash sale countdown timer">
        <div class="vb-countdown-one-unit">
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div class="vb-countdown-one-unit">
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div class="vb-countdown-one-unit">
          <strong data-minutes>00</strong>
          <span>Minutes</span>
        </div>

        <div class="vb-countdown-one-unit">
          <strong data-seconds>00</strong>
          <span>Seconds</span>
        </div>
      </div>

      <p class="vb-countdown-one-status" data-status>Offer ends soon. Grab the deal before the timer reaches zero.</p>

      <a class="vb-countdown-one-button" href="#shop-sale">Shop the Sale</a>
    </div>
  </div>
</div>

CSS

/* CSS is included in the live preview above. */

This flash sale countdown timer is useful for ecommerce sale sections, landing page campaigns, seasonal offers, product promotions, weekly deals, and limited-time discount banners.

2. Event Launch Countdown Timer

An event launch countdown timer is useful for conferences, webinars, workshops, online summits, livestreams, product demos, registration pages, and event landing pages. It helps visitors quickly understand when the next event begins.

This example uses a clean ticket-style layout with a vertical event date panel, event information, compact countdown cards, and a registration-focused call-to-action. The JavaScript calculates the next event in a recurring event cycle, so the example stays active in the future.

LIVE -- Event
Example 02

Event Launch Countdown Timer

Show how much time is left before the next webinar, workshop, product demo, conference, or live online event.

Live Online Event cycle active Free Registration
Next event starts in Loading next date
00 Days
00 Hours
00 Min
00 Sec

Registration is open for the next event.

Reserve Your Seat

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-two]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 14;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-05T18:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");
  const nextLabelEl = timer.querySelector("[data-next-label]");
  const eventMonthEl = timer.querySelector("[data-event-month]");
  const eventDayEl = timer.querySelector("[data-event-day]");
  const eventYearEl = timer.querySelector("[data-event-year]");
  const eventCycleEl = timer.querySelector("[data-event-cycle]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextEventTime(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateEventDate(nextEventTime) {
    const eventDate = new Date(nextEventTime);
    const month = eventDate.toLocaleString("en-US", { month: "short" }).toUpperCase();

    eventMonthEl.textContent = month;
    eventDayEl.textContent = formatNumber(eventDate.getDate());
    eventYearEl.textContent = eventDate.getFullYear();
    nextLabelEl.textContent = "Next event: " + month + " " + eventDate.getDate();
    eventCycleEl.textContent = "Event schedule active";
  }

  function updateCountdown() {
    const now = Date.now();
    const nextEventTime = getNextEventTime(now);
    let remaining = nextEventTime - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);

    updateEventDate(nextEventTime);
    statusEl.textContent = "Registration is open for the next event.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-two-demo">
  <div class="vb-countdown-two-event" data-vb-countdown-two data-cycle-days="14">
    <div class="vb-countdown-two-date">
      <span data-event-month>LIVE</span>
      <strong data-event-day>--</strong>
      <small data-event-year>Event</small>
    </div>

    <div class="vb-countdown-two-info">
      <span class="vb-countdown-two-kicker">Example 02</span>
      <h3>Event Launch Countdown Timer</h3>
      <p>Show how much time is left before the next webinar, workshop, product demo, conference, or live online event.</p>

      <div class="vb-countdown-two-meta">
        <span>Live Online</span>
        <span data-event-cycle>Event cycle active</span>
        <span>Free Registration</span>
      </div>
    </div>

    <div class="vb-countdown-two-box">
      <div class="vb-countdown-two-box-head">
        <strong>Next event starts in</strong>
        <span data-next-label>Loading next date</span>
      </div>

      <div class="vb-countdown-two-timer" aria-label="Event launch countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <p data-status>Registration is open for the next event.</p>
      <a href="#register">Reserve Your Seat</a>
    </div>
  </div>
</div>

CSS

/* CSS is included in the live preview above. */

This event launch countdown timer is useful for webinar landing pages, conference registration pages, online summits, product demos, workshop announcements, livestream events, and recurring event sections.

3. Product Launch Countdown Timer

A product launch countdown timer is useful for app releases, SaaS launches, preorder pages, startup landing pages, coming soon sections, digital product drops, and new feature announcements. It helps build anticipation and shows visitors when the product becomes available.

This example uses a futuristic product launch layout with a dark glass UI, glowing product orb, launch date badge, email signup form, and responsive countdown blocks. The demo calculates the next launch cycle dynamically, so the countdown keeps working in the future.

JS
Example 03

Product Launch Countdown Timer

Use a launch countdown timer for new products, app releases, SaaS features, preorder campaigns, digital products, and startup landing pages.

Next launch date loading Early access opens soon
00 Days
00 Hours
00 Minutes
00 Seconds

The product launch is coming soon. Join the waiting list before early access opens.

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-three]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 30;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-10T10:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextLaunchEl = timer.querySelector("[data-next-launch]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextLaunch(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextLaunch = getNextLaunch(now);
    let remaining = nextLaunch - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const launchDate = new Date(nextLaunch);
    const dateText = launchDate.toLocaleDateString("en-US", {
      month: "short",
      day: "numeric",
      year: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextLaunchEl.textContent = "Next launch: " + dateText;
    statusEl.textContent = "The product launch is coming soon. Join the waiting list before early access opens.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-three-demo">
  <div class="vb-countdown-three-launch" data-vb-countdown-three data-cycle-days="30">
    <div class="vb-countdown-three-visual">
      <div class="vb-countdown-three-grid"></div>
      <div class="vb-countdown-three-orb">
        <span>JS</span>
      </div>
      <div class="vb-countdown-three-ring vb-countdown-three-ring-one"></div>
      <div class="vb-countdown-three-ring vb-countdown-three-ring-two"></div>
      <div class="vb-countdown-three-dot vb-countdown-three-dot-one"></div>
      <div class="vb-countdown-three-dot vb-countdown-three-dot-two"></div>
    </div>

    <div class="vb-countdown-three-content">
      <span class="vb-countdown-three-kicker">Example 03</span>
      <h3>Product Launch Countdown Timer</h3>
      <p>Use a launch countdown timer for new products, app releases, SaaS features, preorder campaigns, digital products, and startup landing pages.</p>

      <div class="vb-countdown-three-launch-meta">
        <span data-next-launch>Next launch date loading</span>
        <strong>Early access opens soon</strong>
      </div>

      <div class="vb-countdown-three-timer" aria-label="Product launch countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Minutes</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Seconds</span>
        </div>
      </div>

      <p class="vb-countdown-three-status" data-status>The product launch is coming soon. Join the waiting list before early access opens.</p>

      <form class="vb-countdown-three-form">
        <input type="email" placeholder="Enter your email" aria-label="Email address">
        <button type="button">Notify Me</button>
      </form>
    </div>
  </div>
</div>

CSS

.vb-countdown-three-demo,
.vb-countdown-three-demo * {
  box-sizing: border-box;
}

.vb-countdown-three-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: clamp(28px, 5vw, 58px) clamp(28px, 5vw, 58px) 14px clamp(28px, 5vw, 58px);
  background:
    radial-gradient(circle at 18% 18%, rgba(34, 211, 238, 0.18), transparent 34%),
    radial-gradient(circle at 84% 20%, rgba(168, 85, 247, 0.22), transparent 36%),
    linear-gradient(135deg, #020617 0%, #111827 52%, #1e1b4b 100%) !important;
  border: 1px solid rgba(125, 211, 252, 0.20);
  box-shadow: 0 34px 96px rgba(2, 6, 23, 0.42);
}

.vb-countdown-three-launch {
  display: grid;
  grid-template-columns: minmax(260px, 0.84fr) minmax(0, 1.16fr);
  gap: clamp(24px, 4vw, 38px);
  align-items: center;
  max-width: 1160px;
  margin: 0 auto;
}

.vb-countdown-three-visual {
  position: relative;
  min-width: 0;
  min-height: clamp(320px, 44vw, 500px);
  overflow: hidden;
  border-radius: clamp(28px, 4vw, 46px) clamp(28px, 4vw, 46px) 10px clamp(28px, 4vw, 46px);
  background:
    radial-gradient(circle at 50% 50%, rgba(34, 211, 238, 0.22), transparent 30%),
    linear-gradient(135deg, rgba(255,255,255,0.09), rgba(255,255,255,0.025)) !important;
  border: 1px solid rgba(255,255,255,0.11);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
}

.vb-countdown-three-grid {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(rgba(125,211,252,0.08) 1px, transparent 1px),
    linear-gradient(90deg, rgba(125,211,252,0.08) 1px, transparent 1px);
  background-size: 34px 34px;
  mask-image: radial-gradient(circle at center, black 0%, transparent 75%);
}

.vb-countdown-three-orb {
  position: absolute;
  left: 50%;
  top: 50%;
  width: clamp(140px, 20vw, 196px);
  height: clamp(140px, 20vw, 196px);
  border-radius: 999px;
  transform: translate(-50%, -50%);
  background:
    radial-gradient(circle at 32% 28%, rgba(255,255,255,0.52), transparent 22%),
    linear-gradient(135deg, #22d3ee, #2563eb 52%, #a855f7) !important;
  box-shadow:
    0 0 44px rgba(34, 211, 238, 0.46),
    0 0 100px rgba(168, 85, 247, 0.30);
}

.vb-countdown-three-orb span {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(40px, 7vw, 58px);
  font-weight: 950;
  letter-spacing: -0.08em;
}

.vb-countdown-three-ring {
  position: absolute;
  left: 50%;
  top: 50%;
  border-radius: 999px;
  transform: translate(-50%, -50%);
  border: 1px solid rgba(125, 211, 252, 0.28);
}

.vb-countdown-three-ring-one {
  width: clamp(220px, 32vw, 320px);
  height: clamp(220px, 32vw, 320px);
}

.vb-countdown-three-ring-two {
  width: clamp(280px, 40vw, 410px);
  height: clamp(280px, 40vw, 410px);
  border-color: rgba(168, 85, 247, 0.25);
}

.vb-countdown-three-dot {
  position: absolute;
  width: 18px;
  height: 18px;
  border-radius: 999px;
}

.vb-countdown-three-dot-one {
  left: 21%;
  top: 28%;
  background: #67e8f9;
  box-shadow: 0 0 24px rgba(103, 232, 249, 0.72);
}

.vb-countdown-three-dot-two {
  right: 18%;
  bottom: 25%;
  background: #c084fc;
  box-shadow: 0 0 24px rgba(192, 132, 252, 0.72);
}

.vb-countdown-three-content {
  min-width: 0;
}

.vb-countdown-three-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(34, 211, 238, 0.12);
  border: 1px solid rgba(125, 211, 252, 0.18);
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-three-content h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5.7vw, 76px) !important;
  line-height: 0.9 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-countdown-three-content p {
  max-width: 680px;
  margin: 0 0 22px !important;
  color: #cbd5e1 !important;
  -webkit-text-fill-color: #cbd5e1 !important;
  font-size: clamp(15px, 1.7vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-three-launch-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
}

.vb-countdown-three-launch-meta span,
.vb-countdown-three-launch-meta strong {
  display: inline-flex;
  align-items: center;
  min-height: 38px;
  padding: 9px 12px;
  border-radius: 999px;
  font-size: 12px;
  line-height: 1.25;
  font-weight: 900;
}

.vb-countdown-three-launch-meta span {
  background: rgba(34, 211, 238, 0.12);
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  border: 1px solid rgba(125, 211, 252, 0.16);
}

.vb-countdown-three-launch-meta strong {
  background: rgba(168, 85, 247, 0.14);
  color: #ddd6fe !important;
  -webkit-text-fill-color: #ddd6fe !important;
  border: 1px solid rgba(196, 181, 253, 0.16);
}

.vb-countdown-three-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 12px);
  margin-bottom: 18px;
}

.vb-countdown-three-timer div {
  min-width: 0;
  position: relative;
  overflow: hidden;
  padding: clamp(14px, 2vw, 18px) clamp(7px, 1.2vw, 10px);
  border-radius: 24px 24px 8px 24px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
}

.vb-countdown-three-timer div::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 3px;
  background: linear-gradient(90deg, #22d3ee, #a855f7);
}

.vb-countdown-three-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 8px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(24px, 3.5vw, 44px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-three-timer span {
  display: block;
  color: #a5f3fc !important;
  -webkit-text-fill-color: #a5f3fc !important;
  font-size: clamp(9px, 1.1vw, 11px);
  font-weight: 950;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.vb-countdown-three-status {
  margin-bottom: 18px !important;
}

.vb-countdown-three-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 140px;
  gap: 10px;
  max-width: 560px;
}

.vb-countdown-three-form input {
  width: 100%;
  min-width: 0;
  min-height: 52px;
  padding: 0 16px;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.14);
  outline: none;
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 700;
}

.vb-countdown-three-form input::placeholder {
  color: rgba(226, 232, 240, 0.72);
  -webkit-text-fill-color: rgba(226, 232, 240, 0.72);
}

.vb-countdown-three-form button {
  min-height: 52px;
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #22d3ee, #a855f7);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(34, 211, 238, 0.22);
}

@media (max-width: 980px) {
  .vb-countdown-three-launch {
    grid-template-columns: 1fr;
  }

  .vb-countdown-three-visual {
    min-height: 360px;
  }

  .vb-countdown-three-timer strong {
    font-size: clamp(30px, 8vw, 52px);
  }
}

@media (max-width: 700px) {
  .vb-countdown-three-content h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-three-visual {
    min-height: 310px;
    border-radius: 28px 28px 8px 28px;
  }

  .vb-countdown-three-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-three-timer strong {
    font-size: clamp(30px, 13vw, 46px);
  }

  .vb-countdown-three-form {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 360px) {
  .vb-countdown-three-timer {
    grid-template-columns: 1fr;
  }
}

This product launch countdown timer is useful for SaaS launch pages, app releases, preorder pages, startup landing pages, digital product drops, feature launches, and coming soon website sections.

4. Daily Deal Countdown Timer

A daily deal countdown timer is useful for ecommerce stores, online marketplaces, product grids, homepage promotions, flash deals, and limited-time product cards. It shows how long today’s deal remains available before the next day starts.

This example uses a compact ecommerce product-card layout with a bright product visual, price block, stock meter, vertical timer strip, and deal message. The JavaScript counts down to the next local midnight, so the demo keeps working day after day.

Example 04
Daily Deal

Daily Deal Countdown Timer

Use this timer for daily ecommerce offers, marketplace deals, limited product discounts, and product cards that refresh every day.

Today only €49
Save 35%
Stock left 18 items
00 Hours
00 Min
00 Sec

Deal resets at midnight.

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-four]");
  if (!timer) return;

  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextMidnight() {
    const deadline = new Date();
    deadline.setHours(24, 0, 0, 0);
    return deadline.getTime();
  }

  function updateCountdown() {
    const now = Date.now();
    const deadline = getNextMidnight();
    let remaining = deadline - now;

    if (remaining <= 0) {
      remaining = 24 * 60 * 60 * 1000;
    }

    const hours = Math.floor(remaining / (1000 * 60 * 60));
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Deal resets at midnight.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-four-demo">
  <div class="vb-countdown-four-card" data-vb-countdown-four>
    <div class="vb-countdown-four-image">
      <span>Example 04</span>
      <div class="vb-countdown-four-product-shape">
        <i></i>
        <i></i>
      </div>
    </div>

    <div class="vb-countdown-four-info">
      <div class="vb-countdown-four-label">Daily Deal</div>
      <h3>Daily Deal Countdown Timer</h3>
      <p>Use this timer for daily ecommerce offers, marketplace deals, limited product discounts, and product cards that refresh every day.</p>

      <div class="vb-countdown-four-price">
        <div>
          <small>Today only</small>
          <strong>€49</strong>
        </div>
        <span>Save 35%</span>
      </div>

      <div class="vb-countdown-four-stock">
        <strong>Stock left</strong>
        <span><i></i></span>
        <em>18 items</em>
      </div>
    </div>

    <div class="vb-countdown-four-timer" aria-label="Daily deal countdown timer">
      <div>
        <strong data-hours>00</strong>
        <span>Hours</span>
      </div>

      <div>
        <strong data-minutes>00</strong>
        <span>Min</span>
      </div>

      <div>
        <strong data-seconds>00</strong>
        <span>Sec</span>
      </div>

      <p data-status>Deal resets at midnight.</p>
    </div>
  </div>
</div>

CSS

.vb-countdown-four-demo,
.vb-countdown-four-demo * {
  box-sizing: border-box;
}

.vb-countdown-four-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 12px;
  background:
    repeating-linear-gradient(45deg, rgba(15, 23, 42, 0.035) 0 10px, transparent 10px 20px),
    linear-gradient(135deg, #f8fafc 0%, #ecfdf5 52%, #ffffff 100%) !important;
  border: 1px solid rgba(16, 185, 129, 0.22);
  box-shadow: 0 28px 80px rgba(5, 150, 105, 0.10);
}

.vb-countdown-four-card {
  display: grid;
  grid-template-columns: minmax(220px, 320px) minmax(0, 1fr) minmax(150px, 190px);
  gap: 18px;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(14px, 2vw, 18px);
  border-radius: 34px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 72px rgba(15, 23, 42, 0.10);
}

.vb-countdown-four-image {
  position: relative;
  min-width: 0;
  min-height: clamp(280px, 35vw, 360px);
  overflow: hidden;
  border-radius: 26px 8px 26px 8px;
  background:
    radial-gradient(circle at 28% 20%, rgba(255,255,255,0.34), transparent 34%),
    linear-gradient(135deg, #064e3b, #10b981 58%, #a7f3d0) !important;
}

.vb-countdown-four-image span {
  position: absolute;
  left: 18px;
  top: 18px;
  z-index: 3;
  display: inline-flex;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.16);
  border: 1px solid rgba(255,255,255,0.22);
  color: #ecfdf5 !important;
  -webkit-text-fill-color: #ecfdf5 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-four-product-shape {
  position: absolute;
  left: 50%;
  top: 54%;
  width: clamp(140px, 18vw, 180px);
  height: clamp(170px, 23vw, 210px);
  border-radius: 30px 30px 12px 12px;
  transform: translate(-50%, -50%) rotate(-6deg);
  background:
    linear-gradient(135deg, rgba(255,255,255,0.72), rgba(255,255,255,0.32)) !important;
  border: 1px solid rgba(255,255,255,0.32);
  box-shadow: 0 28px 70px rgba(6, 78, 59, 0.26);
}

.vb-countdown-four-product-shape::before {
  content: "";
  position: absolute;
  left: 24%;
  top: -20px;
  width: 54%;
  height: 42px;
  border-radius: 24px 24px 0 0;
  border: 12px solid rgba(255,255,255,0.42);
  border-bottom: 0;
}

.vb-countdown-four-product-shape i {
  position: absolute;
  display: block;
  border-radius: 999px;
  background: rgba(6, 78, 59, 0.20);
}

.vb-countdown-four-product-shape i:nth-child(1) {
  left: 18%;
  top: 36%;
  width: 64%;
  height: 14px;
}

.vb-countdown-four-product-shape i:nth-child(2) {
  left: 18%;
  top: 52%;
  width: 46%;
  height: 14px;
}

.vb-countdown-four-info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  padding: 14px 10px;
}

.vb-countdown-four-label {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 8px 18px 8px 18px;
  background: #d1fae5;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-four-info h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.93 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-countdown-four-info p {
  max-width: 590px;
  margin: 0 0 20px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-countdown-four-price {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  max-width: 440px;
  margin-bottom: 16px;
  padding: 16px;
  border-radius: 22px;
  background: #f0fdf4;
  border: 1px solid rgba(16, 185, 129, 0.18);
}

.vb-countdown-four-price small {
  display: block;
  margin-bottom: 4px;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-countdown-four-price strong {
  display: block;
  color: #064e3b !important;
  -webkit-text-fill-color: #064e3b !important;
  font-size: clamp(34px, 5vw, 42px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.055em;
}

.vb-countdown-four-price span {
  display: inline-flex;
  padding: 9px 12px;
  border-radius: 999px;
  background: #059669;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-countdown-four-stock {
  display: grid;
  grid-template-columns: 80px minmax(0, 1fr) 70px;
  gap: 10px;
  align-items: center;
  max-width: 440px;
}

.vb-countdown-four-stock strong,
.vb-countdown-four-stock em {
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 13px;
  font-style: normal;
  font-weight: 850;
}

.vb-countdown-four-stock span {
  overflow: hidden;
  height: 10px;
  border-radius: 999px;
  background: #e2e8f0;
}

.vb-countdown-four-stock i {
  display: block;
  width: 66%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #10b981, #facc15) !important;
}

.vb-countdown-four-timer {
  display: grid;
  gap: 10px;
  align-content: stretch;
  min-width: 0;
  padding: 14px;
  border-radius: 26px;
  background: #0f172a;
}

.vb-countdown-four-timer div {
  display: grid;
  place-items: center;
  min-width: 0;
  padding: 15px 8px;
  border-radius: 18px;
  background:
    radial-gradient(circle at 20% 14%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, rgba(16, 185, 129, 0.30), rgba(34, 197, 94, 0.10)) !important;
  border: 1px solid rgba(255,255,255,0.10);
}

.vb-countdown-four-timer strong {
  display: block;
  max-width: 100%;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 4vw, 38px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-four-timer span {
  display: block;
  margin-top: 4px;
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 11px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-countdown-four-timer p {
  margin: 4px 0 0 !important;
  color: #d1fae5 !important;
  -webkit-text-fill-color: #d1fae5 !important;
  font-size: 13px;
  line-height: 1.5;
  font-weight: 700;
  text-align: center;
}

@media (max-width: 1060px) {
  .vb-countdown-four-card {
    grid-template-columns: minmax(220px, 300px) minmax(0, 1fr);
  }

  .vb-countdown-four-timer {
    grid-column: 1 / -1;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .vb-countdown-four-timer p {
    grid-column: 1 / -1;
  }

  .vb-countdown-four-timer strong {
    font-size: clamp(32px, 7vw, 48px);
  }
}

@media (max-width: 740px) {
  .vb-countdown-four-card {
    grid-template-columns: 1fr;
    border-radius: 26px;
  }

  .vb-countdown-four-info h3 {
    font-size: 36px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-four-price {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-countdown-four-stock {
    grid-template-columns: 1fr;
  }

  .vb-countdown-four-timer {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .vb-countdown-four-timer strong {
    font-size: clamp(28px, 12vw, 42px);
  }
}

@media (max-width: 420px) {
  .vb-countdown-four-timer {
    grid-template-columns: 1fr;
  }
}

This daily deal countdown timer is useful for ecommerce product cards, daily offer sections, marketplace deals, product grids, homepage promotions, flash deal blocks, and limited-time product campaigns.

5. Checkout Reservation Countdown Timer

A checkout reservation countdown timer is useful for ecommerce checkout pages, booking flows, ticket reservations, cart holds, limited inventory purchases, appointment systems, and payment pages where users need to complete an action before a temporary reservation ends.

This example uses a checkout-focused design with a reserved cart message, payment-style summary card, shrinking progress bar, and compact minutes-and-seconds timer. The JavaScript restarts the reservation cycle when it reaches zero, so the demo keeps working in the future.

Example 05

Checkout Reservation Countdown Timer

Use this countdown timer to show how long a cart, ticket, booking, appointment, or checkout session is reserved.

Your checkout reservation is active. Complete checkout before the timer reaches zero.

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-five]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 900;
  let remaining = duration;

  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");
  const progressEl = timer.querySelector("[data-progress]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateTimer() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const minutes = Math.floor(remaining / 60);
    const seconds = remaining % 60;
    const progress = (remaining / duration) * 100;

    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    progressEl.style.width = progress + "%";
    statusEl.textContent = "Your checkout reservation is active. Complete checkout before the timer reaches zero.";

    remaining -= 1;
  }

  updateTimer();
  setInterval(updateTimer, 1000);
})();

HTML

<div class="vb-countdown-five-demo">
  <div class="vb-countdown-five-checkout" data-vb-countdown-five data-duration="900">
    <div class="vb-countdown-five-copy">
      <span class="vb-countdown-five-kicker">Example 05</span>
      <h3>Checkout Reservation Countdown Timer</h3>
      <p>Use this countdown timer to show how long a cart, ticket, booking, appointment, or checkout session is reserved.</p>

      <div class="vb-countdown-five-progress">
        <span data-progress></span>
      </div>

      <p class="vb-countdown-five-status" data-status>Your checkout reservation is active. Complete checkout before the timer reaches zero.</p>
    </div>

    <aside class="vb-countdown-five-card">
      <div class="vb-countdown-five-card-top">
        <strong>Reserved checkout</strong>
        <span>Secure session</span>
      </div>

      <div class="vb-countdown-five-timer" aria-label="Checkout reservation countdown timer">
        <div>
          <strong data-minutes>00</strong>
          <span>Minutes</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Seconds</span>
        </div>
      </div>

      <div class="vb-countdown-five-summary">
        <div>
          <span>Subtotal</span>
          <strong>€129</strong>
        </div>
        <div>
          <span>Discount</span>
          <strong>-€30</strong>
        </div>
        <div>
          <span>Total</span>
          <strong>€99</strong>
        </div>
      </div>

      <button type="button">Continue Checkout</button>
    </aside>
  </div>
</div>

CSS

.vb-countdown-five-demo,
.vb-countdown-five-demo * {
  box-sizing: border-box;
}

.vb-countdown-five-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px 10px 34px 10px;
  background:
    radial-gradient(circle at 12% 18%, rgba(59, 130, 246, 0.18), transparent 34%),
    radial-gradient(circle at 86% 22%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #f0fdf4 52%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.22);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-countdown-five-checkout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(270px, 380px);
  gap: clamp(20px, 3vw, 28px);
  align-items: stretch;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-countdown-five-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  padding: clamp(24px, 4vw, 36px);
  border-radius: 28px 8px 28px 8px;
  background:
    radial-gradient(circle at 18% 16%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #1d4ed8 55%, #047857 100%) !important;
  box-shadow: 0 26px 76px rgba(15, 23, 42, 0.22);
}

.vb-countdown-five-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-five-copy h3 {
  max-width: 760px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5.4vw, 70px) !important;
  line-height: 0.92 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-countdown-five-copy p {
  max-width: 680px;
  margin: 0 0 24px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-five-progress {
  overflow: hidden;
  width: 100%;
  max-width: 620px;
  height: 14px;
  margin-bottom: 18px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.16);
}

.vb-countdown-five-progress span {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #22c55e, #facc15, #f97316) !important;
  transition: width 0.4s linear;
}

.vb-countdown-five-status {
  margin-bottom: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
}

.vb-countdown-five-card {
  min-width: 0;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.12);
}

.vb-countdown-five-card-top {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  margin-bottom: 18px;
}

.vb-countdown-five-card-top strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px;
  line-height: 1.15;
  font-weight: 950;
  letter-spacing: -0.04em;
}

.vb-countdown-five-card-top span {
  display: inline-flex;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dcfce7;
  color: #047857 !important;
  -webkit-text-fill-color: #047857 !important;
  font-size: 11px;
  font-weight: 950;
  white-space: nowrap;
}

.vb-countdown-five-timer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}

.vb-countdown-five-timer div {
  min-width: 0;
  padding: 18px 10px;
  border-radius: 22px;
  background: #eff6ff;
  border: 1px solid rgba(59, 130, 246, 0.18);
  text-align: center;
}

.vb-countdown-five-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: clamp(34px, 7vw, 58px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.06em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-five-timer span {
  display: block;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-five-summary {
  display: grid;
  gap: 10px;
  margin-bottom: 18px;
  padding: 16px;
  border-radius: 22px;
  background: #f8fafc;
}

.vb-countdown-five-summary div {
  display: flex;
  justify-content: space-between;
  gap: 12px;
}

.vb-countdown-five-summary span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  font-weight: 800;
}

.vb-countdown-five-summary strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px;
  font-weight: 950;
}

.vb-countdown-five-card button {
  width: 100%;
  min-height: 48px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #10b981);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 34px rgba(37, 99, 235, 0.24);
}

@media (max-width: 900px) {
  .vb-countdown-five-checkout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-countdown-five-copy {
    border-radius: 22px 8px 22px 8px;
  }

  .vb-countdown-five-copy h3 {
    font-size: 36px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-five-card {
    border-radius: 24px;
  }

  .vb-countdown-five-card-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-countdown-five-timer strong {
    font-size: clamp(34px, 15vw, 52px);
  }
}

@media (max-width: 380px) {
  .vb-countdown-five-timer {
    grid-template-columns: 1fr;
  }
}

This checkout reservation countdown timer is useful for ecommerce checkouts, ticket reservations, cart holds, booking systems, payment pages, appointment flows, and limited inventory purchase pages.

6. Circular Progress Countdown Timer

A circular progress countdown timer is useful for dashboards, app interfaces, fitness sessions, study timers, productivity tools, course lessons, onboarding steps, and modern landing pages that need a visual time progress indicator.

This example uses a circular SVG progress ring, central countdown display, supporting status cards, and a dark app-style interface. The JavaScript restarts the circular timer when it reaches zero, so the demo keeps running instead of stopping.

Example 06

Circular Progress Countdown Timer

Use a circular JavaScript countdown timer for sessions, dashboards, lessons, workouts, productivity blocks, and progress-based UI components.

10 min Session length
Live Timer status
Loop Demo cycle
10:00 Session in progress

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-six]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 600;
  let remaining = duration;

  const timeEl = timer.querySelector("[data-time]");
  const statusEl = timer.querySelector("[data-status]");
  const progressCircle = timer.querySelector("[data-circle-progress]");

  const radius = 92;
  const circumference = 2 * Math.PI * radius;

  progressCircle.style.strokeDasharray = circumference;
  progressCircle.style.strokeDashoffset = 0;

  function formatTime(totalSeconds) {
    const minutes = Math.floor(totalSeconds / 60);
    const seconds = totalSeconds % 60;

    return String(minutes).padStart(2, "0") + ":" + String(seconds).padStart(2, "0");
  }

  function updateTimer() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const progress = remaining / duration;
    const offset = circumference * (1 - progress);

    timeEl.textContent = formatTime(remaining);
    progressCircle.style.strokeDashoffset = offset;
    statusEl.textContent = "Session in progress";

    remaining -= 1;
  }

  updateTimer();
  setInterval(updateTimer, 1000);
})();

HTML

<div class="vb-countdown-six-demo">
  <div class="vb-countdown-six-app" data-vb-countdown-six data-duration="600">
    <div class="vb-countdown-six-content">
      <span class="vb-countdown-six-kicker">Example 06</span>
      <h3>Circular Progress Countdown Timer</h3>
      <p>Use a circular JavaScript countdown timer for sessions, dashboards, lessons, workouts, productivity blocks, and progress-based UI components.</p>

      <div class="vb-countdown-six-cards">
        <div>
          <strong>10 min</strong>
          <span>Session length</span>
        </div>
        <div>
          <strong>Live</strong>
          <span>Timer status</span>
        </div>
        <div>
          <strong>Loop</strong>
          <span>Demo cycle</span>
        </div>
      </div>
    </div>

    <div class="vb-countdown-six-circle-wrap">
      <svg class="vb-countdown-six-svg" viewBox="0 0 220 220" aria-hidden="true">
        <defs>
          <linearGradient id="vbCountdownSixGradient" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" stop-color="#22d3ee"></stop>
            <stop offset="55%" stop-color="#8b5cf6"></stop>
            <stop offset="100%" stop-color="#ec4899"></stop>
          </linearGradient>
        </defs>
        <circle class="vb-countdown-six-track" cx="110" cy="110" r="92"></circle>
        <circle class="vb-countdown-six-progress" cx="110" cy="110" r="92" data-circle-progress></circle>
      </svg>

      <div class="vb-countdown-six-center">
        <strong data-time>10:00</strong>
        <span data-status>Session in progress</span>
      </div>
    </div>
  </div>
</div>

CSS

.vb-countdown-six-demo,
.vb-countdown-six-demo * {
  box-sizing: border-box;
}

.vb-countdown-six-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: clamp(34px, 5vw, 60px);
  background:
    radial-gradient(circle at 16% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(14, 165, 233, 0.18), transparent 34%),
    linear-gradient(135deg, #f5f3ff 0%, #e0f2fe 52%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.22);
  box-shadow: 0 28px 80px rgba(88, 28, 135, 0.11);
}

.vb-countdown-six-app {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 390px);
  gap: clamp(24px, 4vw, 36px);
  align-items: center;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(24px, 4vw, 36px);
  border-radius: clamp(30px, 4vw, 46px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.12), transparent 34%),
    linear-gradient(135deg, #111827 0%, #4c1d95 54%, #0f172a 100%) !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.26);
}

.vb-countdown-six-content {
  min-width: 0;
}

.vb-countdown-six-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 10px 22px 10px 22px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.18);
  color: #ddd6fe !important;
  -webkit-text-fill-color: #ddd6fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-six-content h3 {
  max-width: 740px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5.6vw, 72px) !important;
  line-height: 0.92 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-countdown-six-content p {
  max-width: 680px;
  margin: 0 0 24px !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-six-cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  max-width: 640px;
}

.vb-countdown-six-cards div {
  min-width: 0;
  padding: 18px;
  border-radius: 22px 22px 8px 22px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-countdown-six-cards strong {
  display: block;
  margin-bottom: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(20px, 3vw, 28px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-countdown-six-cards span {
  display: block;
  color: #c4b5fd !important;
  -webkit-text-fill-color: #c4b5fd !important;
  font-size: 12px;
  line-height: 1.35;
  font-weight: 850;
}

.vb-countdown-six-circle-wrap {
  position: relative;
  width: min(100%, 390px);
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  border-radius: 999px;
  background:
    radial-gradient(circle at 50% 50%, rgba(255,255,255,0.12), transparent 48%),
    linear-gradient(135deg, rgba(255,255,255,0.09), rgba(255,255,255,0.03)) !important;
  border: 1px solid rgba(255,255,255,0.12);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
}

.vb-countdown-six-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.vb-countdown-six-track,
.vb-countdown-six-progress {
  fill: none;
  stroke-width: 14;
}

.vb-countdown-six-track {
  stroke: rgba(255,255,255,0.12);
}

.vb-countdown-six-progress {
  stroke: url("#vbCountdownSixGradient");
  stroke-linecap: round;
  transition: stroke-dashoffset 0.4s linear;
}

.vb-countdown-six-center {
  position: absolute;
  inset: 18%;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 10px;
  border-radius: 999px;
  background:
    radial-gradient(circle at 32% 28%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #1e1b4b, #312e81) !important;
  border: 1px solid rgba(255,255,255,0.12);
  text-align: center;
}

.vb-countdown-six-center strong {
  display: block;
  max-width: 100%;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(38px, 9vw, 74px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.06em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-six-center span {
  display: block;
  padding: 0 14px;
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 13px;
  line-height: 1.35;
  font-weight: 850;
}

@media (max-width: 900px) {
  .vb-countdown-six-app {
    grid-template-columns: 1fr;
  }

  .vb-countdown-six-circle-wrap {
    width: min(100%, 340px);
  }
}

@media (max-width: 640px) {
  .vb-countdown-six-content h3 {
    font-size: 36px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-six-cards {
    grid-template-columns: 1fr;
  }

  .vb-countdown-six-circle-wrap {
    width: min(100%, 300px);
  }

  .vb-countdown-six-center strong {
    font-size: clamp(38px, 17vw, 62px);
  }
}

This circular progress countdown timer is useful for dashboard widgets, app interfaces, productivity sessions, study timers, fitness timers, onboarding flows, course lessons, and progress-based landing page sections.

7. Coming Soon Page Countdown Timer

A coming soon page countdown timer is useful for new websites, product previews, app launches, landing pages, brand campaigns, waitlist pages, and pre-launch marketing pages. It gives visitors a clear launch message while collecting attention before the website or product goes live.

This example uses a cinematic coming soon layout with a split dark gradient background, large launch text, glass countdown cards, waitlist form, and decorative light beams. The JavaScript calculates a future launch cycle dynamically, so the demo keeps working instead of stopping on an old date.

Example 07

Coming Soon Page Countdown Timer

Use a polished countdown section for website launches, new product previews, app waitlists, brand campaigns, and coming soon landing pages.

Launch date loading

Launch Countdown Coming soon
00 Days
00 Hours
00 Minutes
00 Seconds

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-seven]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 21;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-07T09:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextLaunchEl = timer.querySelector("[data-next-launch]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextLaunch(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextLaunch = getNextLaunch(now);
    let remaining = nextLaunch - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextLaunch).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric",
      year: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextLaunchEl.textContent = "Next launch window: " + dateText;
    statusEl.textContent = "Coming soon";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-seven-demo">
  <section class="vb-countdown-seven-coming" data-vb-countdown-seven data-cycle-days="21">
    <div class="vb-countdown-seven-copy">
      <span class="vb-countdown-seven-kicker">Example 07</span>
      <h3>Coming Soon Page Countdown Timer</h3>
      <p>Use a polished countdown section for website launches, new product previews, app waitlists, brand campaigns, and coming soon landing pages.</p>

      <form class="vb-countdown-seven-form">
        <input type="email" placeholder="Join the waitlist" aria-label="Email address">
        <button type="button">Notify Me</button>
      </form>

      <p class="vb-countdown-seven-note" data-next-launch>Launch date loading</p>
    </div>

    <div class="vb-countdown-seven-panel">
      <div class="vb-countdown-seven-panel-top">
        <span>Launch Countdown</span>
        <strong data-status>Coming soon</strong>
      </div>

      <div class="vb-countdown-seven-timer" aria-label="Coming soon countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Minutes</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Seconds</span>
        </div>
      </div>
    </div>
  </section>
</div>

CSS

.vb-countdown-seven-demo,
.vb-countdown-seven-demo * {
  box-sizing: border-box;
}

.vb-countdown-seven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 18px 58px 58px 18px;
  background:
    radial-gradient(circle at 12% 18%, rgba(59, 130, 246, 0.22), transparent 32%),
    radial-gradient(circle at 88% 22%, rgba(217, 70, 239, 0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #1e1b4b 54%, #020617 100%) !important;
  border: 1px solid rgba(147, 197, 253, 0.18);
  box-shadow: 0 34px 96px rgba(2, 6, 23, 0.38);
}

.vb-countdown-seven-coming {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 460px);
  gap: clamp(24px, 4vw, 40px);
  align-items: center;
  max-width: 1160px;
  min-height: 520px;
  margin: 0 auto;
  overflow: hidden;
}

.vb-countdown-seven-coming::before {
  content: "";
  position: absolute;
  left: 46%;
  top: -20%;
  width: 2px;
  height: 150%;
  background: linear-gradient(to bottom, transparent, rgba(125, 211, 252, 0.54), transparent);
  transform: rotate(18deg);
  box-shadow: 0 0 48px rgba(125, 211, 252, 0.42);
}

.vb-countdown-seven-coming::after {
  content: "";
  position: absolute;
  right: 8%;
  bottom: 8%;
  width: 180px;
  height: 180px;
  border-radius: 999px;
  background: rgba(217, 70, 239, 0.14);
  filter: blur(2px);
}

.vb-countdown-seven-copy,
.vb-countdown-seven-panel {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-countdown-seven-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 4px 18px 18px 4px;
  background: rgba(59, 130, 246, 0.16);
  border: 1px solid rgba(147, 197, 253, 0.20);
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.vb-countdown-seven-copy h3 {
  max-width: 780px;
  margin: 0 0 18px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 82px) !important;
  line-height: 0.88 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-seven-copy p {
  max-width: 650px;
  margin: 0 0 24px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: clamp(15px, 1.8vw, 17px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-seven-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 140px;
  gap: 10px;
  max-width: 560px;
  margin-bottom: 14px;
}

.vb-countdown-seven-form input {
  width: 100%;
  min-width: 0;
  min-height: 52px;
  padding: 0 16px;
  border-radius: 14px 999px 999px 14px;
  border: 1px solid rgba(191, 219, 254, 0.18);
  outline: none;
  background: rgba(255,255,255,0.08);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-countdown-seven-form input::placeholder {
  color: rgba(219, 234, 254, 0.72);
  -webkit-text-fill-color: rgba(219, 234, 254, 0.72);
}

.vb-countdown-seven-form button {
  min-height: 52px;
  border: 0;
  border-radius: 999px 14px 14px 999px;
  background: linear-gradient(135deg, #3b82f6, #d946ef);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 36px rgba(59, 130, 246, 0.26);
}

.vb-countdown-seven-note {
  margin: 0 !important;
  color: #c4b5fd !important;
  -webkit-text-fill-color: #c4b5fd !important;
  font-size: 13px !important;
  font-weight: 850 !important;
}

.vb-countdown-seven-panel {
  padding: clamp(18px, 3vw, 26px);
  border-radius: 36px 12px 36px 12px;
  background:
    radial-gradient(circle at 24% 18%, rgba(255,255,255,0.14), transparent 34%),
    linear-gradient(135deg, rgba(255,255,255,0.13), rgba(255,255,255,0.045)) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 26px 76px rgba(2, 6, 23, 0.28);
  backdrop-filter: blur(14px);
}

.vb-countdown-seven-panel-top {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  align-items: center;
  margin-bottom: 18px;
}

.vb-countdown-seven-panel-top span {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 950;
}

.vb-countdown-seven-panel-top strong {
  color: #f0abfc !important;
  -webkit-text-fill-color: #f0abfc !important;
  font-size: 13px;
  font-weight: 900;
  text-align: right;
}

.vb-countdown-seven-timer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-countdown-seven-timer div {
  min-width: 0;
  padding: clamp(16px, 3vw, 24px) 10px;
  border-radius: 22px 8px 22px 8px;
  background: rgba(15, 23, 42, 0.72);
  border: 1px solid rgba(255,255,255,0.11);
  text-align: center;
}

.vb-countdown-seven-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 8px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 7vw, 58px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-seven-timer span {
  display: block;
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

@media (max-width: 900px) {
  .vb-countdown-seven-coming {
    grid-template-columns: 1fr;
    min-height: auto;
  }
}

@media (max-width: 640px) {
  .vb-countdown-seven-demo {
    border-radius: 16px 34px 34px 16px;
  }

  .vb-countdown-seven-copy h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-seven-form {
    grid-template-columns: 1fr;
  }

  .vb-countdown-seven-form input,
  .vb-countdown-seven-form button {
    border-radius: 999px;
  }

  .vb-countdown-seven-panel-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-countdown-seven-panel-top strong {
    text-align: left;
  }
}

@media (max-width: 380px) {
  .vb-countdown-seven-timer {
    grid-template-columns: 1fr;
  }
}

This coming soon page countdown timer is useful for website launches, waitlist landing pages, app previews, product teasers, brand campaigns, SaaS launch pages, and pre-release marketing sections.

8. Webinar Registration Countdown Timer

A webinar registration countdown timer is useful for online workshops, training events, live classes, product demos, virtual summits, coaching sessions, and educational landing pages. It helps visitors see how much time remains before the next registration window closes.

This example uses a clean webinar registration card with a speaker section, schedule details, seat counter, countdown sidebar, and registration button. The JavaScript uses a repeating weekly webinar schedule, so the timer keeps working over time.

W
Live Training Webinar Registration Countdown Timer

Webinar Registration Countdown Timer

Use this countdown timer for webinar landing pages, live product demos, workshops, online lessons, training sessions, and virtual event registration pages.

Format Live Online
Seats 42 left
Access Free

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-eight]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 7;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-06T17:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextWebinarEl = timer.querySelector("[data-next-webinar]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextWebinar(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextWebinar = getNextWebinar(now);
    let remaining = nextWebinar - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextWebinar).toLocaleDateString("en-US", {
      weekday: "short",
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextWebinarEl.textContent = "Next webinar: " + dateText;
    statusEl.textContent = "Registration is open for the next webinar.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-eight-demo">
  <section class="vb-countdown-eight-webinar" data-vb-countdown-eight data-cycle-days="7">
    <div class="vb-countdown-eight-main">
      <div class="vb-countdown-eight-speaker">
        <div class="vb-countdown-eight-avatar">W</div>
        <div>
          <span>Live Training</span>
          <strong>Webinar Registration Countdown Timer</strong>
        </div>
      </div>

      <h3>Webinar Registration Countdown Timer</h3>
      <p>Use this countdown timer for webinar landing pages, live product demos, workshops, online lessons, training sessions, and virtual event registration pages.</p>

      <div class="vb-countdown-eight-details">
        <div>
          <span>Format</span>
          <strong>Live Online</strong>
        </div>
        <div>
          <span>Seats</span>
          <strong>42 left</strong>
        </div>
        <div>
          <span>Access</span>
          <strong>Free</strong>
        </div>
      </div>
    </div>

    <aside class="vb-countdown-eight-countbox">
      <span class="vb-countdown-eight-kicker">Example 08</span>
      <strong class="vb-countdown-eight-date" data-next-webinar>Next webinar loading</strong>

      <div class="vb-countdown-eight-timer" aria-label="Webinar registration countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <p data-status>Registration is open for the next webinar.</p>
      <a href="#webinar-registration">Register Now</a>
    </aside>
  </section>
</div>

CSS

.vb-countdown-eight-demo,
.vb-countdown-eight-demo * {
  box-sizing: border-box;
}

.vb-countdown-eight-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 46px 46px 46px 10px;
  background:
    radial-gradient(circle at 14% 18%, rgba(14, 165, 233, 0.18), transparent 34%),
    radial-gradient(circle at 88% 24%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #eef2ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.20);
  box-shadow: 0 28px 80px rgba(49, 46, 129, 0.10);
}

.vb-countdown-eight-webinar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(270px, 360px);
  gap: clamp(20px, 3vw, 28px);
  align-items: stretch;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-countdown-eight-main {
  min-width: 0;
  padding: clamp(24px, 4vw, 38px);
  border-radius: 34px 34px 34px 8px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
}

.vb-countdown-eight-speaker {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 22px;
}

.vb-countdown-eight-avatar {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  border-radius: 18px 18px 18px 4px;
  background: linear-gradient(135deg, #0ea5e9, #6366f1);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  font-weight: 950;
  box-shadow: 0 14px 32px rgba(99, 102, 241, 0.22);
}

.vb-countdown-eight-speaker span {
  display: block;
  margin-bottom: 4px;
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-eight-speaker strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  line-height: 1.25;
  font-weight: 900;
}

.vb-countdown-eight-main h3 {
  margin: 0 0 16px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5.4vw, 68px) !important;
  line-height: 0.92 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-countdown-eight-main p {
  max-width: 690px;
  margin: 0 0 24px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-eight-details {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-countdown-eight-details div {
  min-width: 0;
  padding: 16px;
  border-radius: 20px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.18);
}

.vb-countdown-eight-details span {
  display: block;
  margin-bottom: 6px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-eight-details strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(18px, 3vw, 24px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.04em;
}

.vb-countdown-eight-countbox {
  min-width: 0;
  display: grid;
  align-content: center;
  gap: 16px;
  padding: clamp(20px, 3vw, 26px);
  border-radius: 34px 8px 34px 34px;
  background:
    radial-gradient(circle at 18% 16%, rgba(255,255,255,0.14), transparent 34%),
    linear-gradient(135deg, #0f172a, #312e81 58%, #1e1b4b) !important;
  box-shadow: 0 24px 70px rgba(49, 46, 129, 0.20);
}

.vb-countdown-eight-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-eight-date {
  display: block;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  line-height: 1.2;
  font-weight: 950;
  letter-spacing: -0.04em;
}

.vb-countdown-eight-timer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.vb-countdown-eight-timer div {
  min-width: 0;
  padding: 16px 8px;
  border-radius: 18px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.12);
  text-align: center;
}

.vb-countdown-eight-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 6vw, 48px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-eight-timer span {
  display: block;
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-eight-countbox p {
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-countdown-eight-countbox a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 999px;
  background: linear-gradient(135deg, #0ea5e9, #6366f1);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(14, 165, 233, 0.24);
}

@media (max-width: 900px) {
  .vb-countdown-eight-webinar {
    grid-template-columns: 1fr;
  }

  .vb-countdown-eight-timer {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .vb-countdown-eight-timer strong {
    font-size: clamp(26px, 6vw, 42px);
  }
}

@media (max-width: 640px) {
  .vb-countdown-eight-demo {
    border-radius: 32px 32px 32px 8px;
  }

  .vb-countdown-eight-main h3 {
    font-size: 36px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-eight-details {
    grid-template-columns: 1fr;
  }

  .vb-countdown-eight-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-eight-countbox a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-eight-timer {
    grid-template-columns: 1fr;
  }
}

This webinar registration countdown timer is useful for webinar landing pages, live product demos, online workshops, training pages, course launches, virtual event registration pages, and coaching session campaigns.

9. Black Friday Sale Countdown Timer

A Black Friday sale countdown timer is useful for seasonal ecommerce campaigns, Black Friday landing pages, Cyber Monday promotions, holiday discounts, product sale sections, and limited-time offer banners. It helps highlight a major campaign deadline with strong visual urgency.

This example uses a dark retail campaign layout with neon sale styling, large discount text, angled promo cards, responsive countdown blocks, and a strong shopping CTA. The JavaScript calculates the next yearly Black Friday campaign window, so the demo keeps working year after year.

Example 09

Black Friday Sale Countdown Timer

Use a high-impact countdown timer for Black Friday sales, Cyber Monday promotions, seasonal ecommerce campaigns, and limited holiday offers.

70% OFF Seasonal ecommerce promotion
View Deals
Sale starts in Black Friday loading
00 Days
00 Hours
00 Min
00 Sec

Black Friday campaign countdown is active.

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-nine]");
  if (!timer) return;

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextSaleEl = timer.querySelector("[data-next-sale]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getBlackFriday(year) {
    const date = new Date(year, 10, 1, 0, 0, 0);
    let fridayCount = 0;

    while (date.getMonth() === 10) {
      if (date.getDay() === 5) {
        fridayCount += 1;
        if (fridayCount === 4) {
          date.setHours(0, 0, 0, 0);
          return date.getTime();
        }
      }

      date.setDate(date.getDate() + 1);
    }

    return new Date(year, 10, 29, 0, 0, 0).getTime();
  }

  function getNextBlackFriday(now) {
    const currentYear = new Date(now).getFullYear();
    const thisYear = getBlackFriday(currentYear);

    if (thisYear > now) {
      return thisYear;
    }

    return getBlackFriday(currentYear + 1);
  }

  function updateCountdown() {
    const now = Date.now();
    const nextSale = getNextBlackFriday(now);
    let remaining = nextSale - now;

    if (remaining <= 0) {
      remaining = getBlackFriday(new Date(now).getFullYear() + 1) - now;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const saleDate = new Date(nextSale).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric",
      year: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextSaleEl.textContent = "Next Black Friday: " + saleDate;
    statusEl.textContent = "Black Friday campaign countdown is active.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-nine-demo">
  <section class="vb-countdown-nine-sale" data-vb-countdown-nine>
    <div class="vb-countdown-nine-left">
      <span class="vb-countdown-nine-kicker">Example 09</span>
      <h3>Black Friday Sale Countdown Timer</h3>
      <p>Use a high-impact countdown timer for Black Friday sales, Cyber Monday promotions, seasonal ecommerce campaigns, and limited holiday offers.</p>

      <div class="vb-countdown-nine-discount">
        <strong>70% OFF</strong>
        <span>Seasonal ecommerce promotion</span>
      </div>

      <a href="#black-friday-sale" class="vb-countdown-nine-button">View Deals</a>
    </div>

    <div class="vb-countdown-nine-right">
      <div class="vb-countdown-nine-card">
        <div class="vb-countdown-nine-card-top">
          <span>Sale starts in</span>
          <strong data-next-sale>Black Friday loading</strong>
        </div>

        <div class="vb-countdown-nine-timer" aria-label="Black Friday sale countdown timer">
          <div>
            <strong data-days>00</strong>
            <span>Days</span>
          </div>

          <div>
            <strong data-hours>00</strong>
            <span>Hours</span>
          </div>

          <div>
            <strong data-minutes>00</strong>
            <span>Min</span>
          </div>

          <div>
            <strong data-seconds>00</strong>
            <span>Sec</span>
          </div>
        </div>

        <p data-status>Black Friday campaign countdown is active.</p>
      </div>
    </div>
  </section>
</div>

CSS

.vb-countdown-nine-demo,
.vb-countdown-nine-demo * {
  box-sizing: border-box;
}

.vb-countdown-nine-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 10px 10px 54px 54px;
  background:
    repeating-linear-gradient(-45deg, rgba(255,255,255,0.04) 0 8px, transparent 8px 18px),
    radial-gradient(circle at 18% 22%, rgba(239, 68, 68, 0.22), transparent 34%),
    radial-gradient(circle at 88% 16%, rgba(250, 204, 21, 0.16), transparent 34%),
    linear-gradient(135deg, #020617 0%, #111827 48%, #450a0a 100%) !important;
  border: 1px solid rgba(248, 113, 113, 0.22);
  box-shadow: 0 34px 96px rgba(2, 6, 23, 0.42);
}

.vb-countdown-nine-sale {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(280px, 0.75fr);
  gap: clamp(24px, 4vw, 42px);
  align-items: center;
  max-width: 1160px;
  margin: 0 auto;
  overflow: hidden;
}

.vb-countdown-nine-sale::before {
  content: "";
  position: absolute;
  left: 46%;
  top: 4%;
  width: 140px;
  height: 92%;
  background: rgba(250, 204, 21, 0.10);
  transform: skewX(-16deg);
  filter: blur(1px);
}

.vb-countdown-nine-left,
.vb-countdown-nine-right {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-countdown-nine-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 2px 16px 2px 16px;
  background: rgba(239, 68, 68, 0.18);
  border: 1px solid rgba(248, 113, 113, 0.26);
  color: #fecaca !important;
  -webkit-text-fill-color: #fecaca !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.vb-countdown-nine-left h3 {
  max-width: 820px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6.2vw, 86px) !important;
  line-height: 0.86 !important;
  font-weight: 950 !important;
  letter-spacing: -0.085em;
  overflow-wrap: anywhere;
}

.vb-countdown-nine-left p {
  max-width: 660px;
  margin: 0 0 22px !important;
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: clamp(15px, 1.8vw, 17px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-nine-discount {
  display: inline-grid;
  gap: 5px;
  max-width: 100%;
  margin-bottom: 18px;
  padding: 18px 20px;
  border-radius: 4px 28px 4px 28px;
  background: linear-gradient(135deg, rgba(239, 68, 68, 0.30), rgba(250, 204, 21, 0.14)) !important;
  border: 1px solid rgba(250, 204, 21, 0.24);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10);
}

.vb-countdown-nine-discount strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 60px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.07em;
}

.vb-countdown-nine-discount span {
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 13px;
  line-height: 1.35;
  font-weight: 850;
}

.vb-countdown-nine-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 14px 20px;
  border-radius: 4px 999px 999px 4px;
  background: linear-gradient(135deg, #ef4444, #f97316 55%, #facc15);
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 18px 40px rgba(239, 68, 68, 0.28);
}

.vb-countdown-nine-right {
  display: flex;
  justify-content: center;
}

.vb-countdown-nine-card {
  width: min(100%, 430px);
  padding: clamp(20px, 3vw, 28px);
  border-radius: 38px 8px 38px 8px;
  background:
    radial-gradient(circle at 18% 14%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, rgba(255,255,255,0.12), rgba(255,255,255,0.045)) !important;
  border: 1px solid rgba(255,255,255,0.14);
  backdrop-filter: blur(14px);
  box-shadow: 0 28px 80px rgba(0,0,0,0.24);
}

.vb-countdown-nine-card-top {
  display: grid;
  gap: 7px;
  margin-bottom: 18px;
}

.vb-countdown-nine-card-top span {
  color: #fecaca !important;
  -webkit-text-fill-color: #fecaca !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-nine-card-top strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  line-height: 1.2;
  font-weight: 950;
  letter-spacing: -0.04em;
}

.vb-countdown-nine-timer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}

.vb-countdown-nine-timer div {
  min-width: 0;
  padding: 18px 10px;
  border-radius: 8px 22px 8px 22px;
  background: #ffffff;
  border: 1px solid rgba(255,255,255,0.16);
  text-align: center;
  box-shadow: 0 18px 42px rgba(0,0,0,0.18);
}

.vb-countdown-nine-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 6px;
  color: #991b1b !important;
  -webkit-text-fill-color: #991b1b !important;
  font-size: clamp(34px, 7vw, 56px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-nine-timer span {
  display: block;
  color: #7f1d1d !important;
  -webkit-text-fill-color: #7f1d1d !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-nine-card p {
  margin: 0 !important;
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 700;
}

@media (max-width: 920px) {
  .vb-countdown-nine-sale {
    grid-template-columns: 1fr;
  }

  .vb-countdown-nine-right {
    justify-content: flex-start;
  }
}

@media (max-width: 640px) {
  .vb-countdown-nine-demo {
    border-radius: 10px 10px 34px 34px;
  }

  .vb-countdown-nine-left h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-nine-button {
    width: 100%;
    border-radius: 999px;
  }
}

@media (max-width: 380px) {
  .vb-countdown-nine-timer {
    grid-template-columns: 1fr;
  }
}

This Black Friday sale countdown timer is useful for ecommerce campaign pages, holiday sale banners, Cyber Monday promotions, product discount sections, seasonal landing pages, and major retail offer pages.

10. Limited Stock Countdown Timer

A limited stock countdown timer is useful for ecommerce product pages, product cards, drops, flash inventory alerts, preorder campaigns, ticket sales, and limited quantity offers. It combines time urgency with stock availability so visitors can see both deadline and inventory status.

This example uses an inventory-focused layout with a stock meter, product availability badge, compact countdown display, and action button. The JavaScript uses a short repeating inventory window, so the demo never stays stuck after reaching zero.

Example 10
Limited Stock

Limited Stock Countdown Timer

Use this countdown timer for product pages, limited drops, inventory campaigns, product cards, ticket sales, and preorder availability sections.

Only 12 items left Low stock

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-ten]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 3600;
  let remaining = duration;

  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const hours = Math.floor(remaining / (1000 * 60 * 60));
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Inventory hold is active.";

    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-ten-demo">
  <section class="vb-countdown-ten-stock" data-vb-countdown-ten data-duration="3600">
    <div class="vb-countdown-ten-product">
      <div class="vb-countdown-ten-image">
        <span>Example 10</span>
        <div class="vb-countdown-ten-box">
          <i></i>
          <i></i>
          <i></i>
        </div>
      </div>

      <div class="vb-countdown-ten-info">
        <div class="vb-countdown-ten-label">Limited Stock</div>
        <h3>Limited Stock Countdown Timer</h3>
        <p>Use this countdown timer for product pages, limited drops, inventory campaigns, product cards, ticket sales, and preorder availability sections.</p>

        <div class="vb-countdown-ten-meter">
          <div class="vb-countdown-ten-meter-head">
            <strong>Only 12 items left</strong>
            <span>Low stock</span>
          </div>
          <div class="vb-countdown-ten-meter-bar">
            <i></i>
          </div>
        </div>
      </div>
    </div>

    <aside class="vb-countdown-ten-sidebar">
      <span class="vb-countdown-ten-kicker">Stock window</span>
      <strong class="vb-countdown-ten-title">Reserve before timer ends</strong>

      <div class="vb-countdown-ten-timer" aria-label="Limited stock countdown timer">
        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <p data-status>Inventory hold is active.</p>
      <a href="#reserve-stock">Reserve Stock</a>
    </aside>
  </section>
</div>

CSS

.vb-countdown-ten-demo,
.vb-countdown-ten-demo * {
  box-sizing: border-box;
}

.vb-countdown-ten-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 8px 48px 8px 48px;
  background:
    radial-gradient(circle at 12% 18%, rgba(245, 158, 11, 0.18), transparent 34%),
    radial-gradient(circle at 86% 24%, rgba(20, 184, 166, 0.18), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #f0fdfa 52%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.24);
  box-shadow: 0 28px 80px rgba(120, 53, 15, 0.10);
}

.vb-countdown-ten-stock {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(250px, 330px);
  gap: clamp(18px, 3vw, 26px);
  align-items: stretch;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-countdown-ten-product {
  display: grid;
  grid-template-columns: minmax(220px, 310px) minmax(0, 1fr);
  gap: 18px;
  min-width: 0;
  padding: clamp(14px, 2vw, 18px);
  border-radius: 30px 8px 30px 8px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.09);
}

.vb-countdown-ten-image {
  position: relative;
  min-width: 0;
  min-height: clamp(260px, 32vw, 340px);
  overflow: hidden;
  border-radius: 24px 8px 24px 8px;
  background:
    radial-gradient(circle at 30% 18%, rgba(255,255,255,0.30), transparent 34%),
    linear-gradient(135deg, #92400e, #f59e0b 52%, #14b8a6) !important;
}

.vb-countdown-ten-image > span {
  position: absolute;
  left: 16px;
  top: 16px;
  z-index: 3;
  display: inline-flex;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.16);
  border: 1px solid rgba(255,255,255,0.22);
  color: #fffbeb !important;
  -webkit-text-fill-color: #fffbeb !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-ten-box {
  position: absolute;
  left: 50%;
  top: 55%;
  width: clamp(150px, 20vw, 200px);
  height: clamp(150px, 20vw, 200px);
  transform: translate(-50%, -50%) rotate(8deg);
  border-radius: 28px 10px 28px 10px;
  background:
    linear-gradient(135deg, rgba(255,255,255,0.70), rgba(255,255,255,0.30)) !important;
  border: 1px solid rgba(255,255,255,0.34);
  box-shadow: 0 28px 68px rgba(120, 53, 15, 0.24);
}

.vb-countdown-ten-box i {
  position: absolute;
  display: block;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.18);
}

.vb-countdown-ten-box i:nth-child(1) {
  left: 24px;
  top: 42px;
  width: 60%;
  height: 14px;
}

.vb-countdown-ten-box i:nth-child(2) {
  left: 24px;
  top: 78px;
  width: 46%;
  height: 14px;
}

.vb-countdown-ten-box i:nth-child(3) {
  left: 24px;
  bottom: 32px;
  width: 32%;
  height: 28px;
  border-radius: 10px;
  background: rgba(20, 184, 166, 0.30);
}

.vb-countdown-ten-info {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  padding: 12px 8px;
}

.vb-countdown-ten-label {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 999px 999px 999px 4px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-ten-info h3 {
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5vw, 62px) !important;
  line-height: 0.93 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-countdown-ten-info p {
  max-width: 590px;
  margin: 0 0 20px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.72;
  font-weight: 650;
}

.vb-countdown-ten-meter {
  max-width: 480px;
  padding: 16px;
  border-radius: 22px;
  background: #fffbeb;
  border: 1px solid rgba(245, 158, 11, 0.22);
}

.vb-countdown-ten-meter-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.vb-countdown-ten-meter-head strong {
  color: #78350f !important;
  -webkit-text-fill-color: #78350f !important;
  font-size: 14px;
  font-weight: 950;
}

.vb-countdown-ten-meter-head span {
  color: #b45309 !important;
  -webkit-text-fill-color: #b45309 !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-ten-meter-bar {
  overflow: hidden;
  height: 12px;
  border-radius: 999px;
  background: #fde68a;
}

.vb-countdown-ten-meter-bar i {
  display: block;
  width: 32%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #ef4444, #f59e0b) !important;
}

.vb-countdown-ten-sidebar {
  min-width: 0;
  display: grid;
  align-content: center;
  gap: 16px;
  padding: clamp(20px, 3vw, 26px);
  border-radius: 8px 34px 8px 34px;
  background:
    radial-gradient(circle at 24% 16%, rgba(255,255,255,0.14), transparent 34%),
    linear-gradient(135deg, #0f172a, #134e4a 58%, #042f2e) !important;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.18);
}

.vb-countdown-ten-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(20, 184, 166, 0.14);
  border: 1px solid rgba(94, 234, 212, 0.20);
  color: #99f6e4 !important;
  -webkit-text-fill-color: #99f6e4 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-ten-title {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 26px;
  line-height: 1.08;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-countdown-ten-timer {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}

.vb-countdown-ten-timer div {
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 16px;
  border-radius: 18px 6px 18px 6px;
  background: rgba(255,255,255,0.09);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-countdown-ten-timer strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 6vw, 44px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-ten-timer span {
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-ten-sidebar p {
  margin: 0 !important;
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-countdown-ten-sidebar a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 999px;
  background: linear-gradient(135deg, #f59e0b, #14b8a6);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(20, 184, 166, 0.24);
}

@media (max-width: 980px) {
  .vb-countdown-ten-stock {
    grid-template-columns: 1fr;
  }

  .vb-countdown-ten-timer {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .vb-countdown-ten-timer div {
    display: grid;
    place-items: center;
    text-align: center;
  }
}

@media (max-width: 760px) {
  .vb-countdown-ten-product {
    grid-template-columns: 1fr;
  }

  .vb-countdown-ten-info h3 {
    font-size: 36px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-ten-meter-head {
    flex-direction: column;
  }

  .vb-countdown-ten-sidebar a {
    width: 100%;
  }
}

@media (max-width: 420px) {
  .vb-countdown-ten-timer {
    grid-template-columns: 1fr;
  }
}

This limited stock countdown timer is useful for ecommerce product pages, product cards, limited inventory campaigns, preorder drops, ticket availability sections, and flash stock alerts.

11. Sticky Top Bar Countdown Timer

A sticky top bar countdown timer is useful for ecommerce stores, SaaS websites, course launches, limited offers, announcement bars, free shipping campaigns, and site-wide promotions. It keeps the deadline visible without taking over the full page.

This example uses a slim sticky-style announcement bar with a compact countdown, promo message, pill CTA button, and responsive stacking for smaller screens. The JavaScript uses a repeating 6-hour campaign window, so the demo keeps running and never stays stopped.

Example 11 Limited offer ends soon

Use this sticky top bar countdown for site-wide sale messages, free shipping offers, and promotional campaigns.

00 Hours
00 Min
00 Sec
Claim Offer

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-eleven]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 21600;
  let remaining = duration;

  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const hours = Math.floor(remaining / 3600);
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);

    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-eleven-demo">
  <div class="vb-countdown-eleven-bar" data-vb-countdown-eleven data-duration="21600">
    <div class="vb-countdown-eleven-message">
      <span class="vb-countdown-eleven-kicker">Example 11</span>
      <strong>Limited offer ends soon</strong>
      <p>Use this sticky top bar countdown for site-wide sale messages, free shipping offers, and promotional campaigns.</p>
    </div>

    <div class="vb-countdown-eleven-timer" aria-label="Sticky top bar countdown timer">
      <div>
        <strong data-hours>00</strong>
        <span>Hours</span>
      </div>

      <div>
        <strong data-minutes>00</strong>
        <span>Min</span>
      </div>

      <div>
        <strong data-seconds>00</strong>
        <span>Sec</span>
      </div>
    </div>

    <a href="#top-bar-offer" class="vb-countdown-eleven-button">Claim Offer</a>
  </div>
</div>

CSS

.vb-countdown-eleven-demo,
.vb-countdown-eleven-demo * {
  box-sizing: border-box;
}

.vb-countdown-eleven-demo {
  margin: 28px 0;
  padding: clamp(16px, 3vw, 30px);
  border-radius: 32px 32px 8px 8px;
  background:
    linear-gradient(135deg, #f8fafc 0%, #fff7ed 50%, #ffffff 100%) !important;
  border: 1px solid rgba(249, 115, 22, 0.20);
  box-shadow: 0 24px 70px rgba(124, 45, 18, 0.09);
}

.vb-countdown-eleven-bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: clamp(14px, 2.5vw, 22px);
  align-items: center;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(14px, 2vw, 18px);
  border-radius: 999px;
  background:
    radial-gradient(circle at 12% 50%, rgba(251, 146, 60, 0.28), transparent 34%),
    linear-gradient(135deg, #111827 0%, #7c2d12 48%, #ea580c 100%) !important;
  border: 1px solid rgba(255,255,255,0.12);
  box-shadow: 0 24px 64px rgba(15, 23, 42, 0.22);
}

.vb-countdown-eleven-message {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  min-width: 0;
}

.vb-countdown-eleven-kicker {
  display: inline-flex;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  white-space: nowrap;
}

.vb-countdown-eleven-message strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(15px, 2vw, 18px);
  line-height: 1.2;
  font-weight: 950;
  letter-spacing: -0.035em;
  white-space: nowrap;
}

.vb-countdown-eleven-message p {
  min-width: 0;
  margin: 0 !important;
  color: #ffedd5 !important;
  -webkit-text-fill-color: #ffedd5 !important;
  font-size: 13px;
  line-height: 1.35;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-countdown-eleven-timer {
  display: grid;
  grid-template-columns: repeat(3, minmax(54px, 1fr));
  gap: 8px;
  min-width: 0;
}

.vb-countdown-eleven-timer div {
  min-width: 0;
  padding: 9px 8px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  text-align: center;
}

.vb-countdown-eleven-timer strong {
  display: block;
  margin-bottom: 2px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(18px, 2.6vw, 26px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
}

.vb-countdown-eleven-timer span {
  display: block;
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 9px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-eleven-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 12px 17px;
  border-radius: 999px;
  background: #ffffff;
  color: #9a3412 !important;
  -webkit-text-fill-color: #9a3412 !important;
  text-decoration: none !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
  box-shadow: 0 14px 30px rgba(15, 23, 42, 0.18);
}

@media (max-width: 980px) {
  .vb-countdown-eleven-bar {
    grid-template-columns: 1fr;
    border-radius: 28px;
  }

  .vb-countdown-eleven-message {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .vb-countdown-eleven-message p {
    grid-column: 1 / -1;
  }

  .vb-countdown-eleven-button {
    width: 100%;
  }
}

@media (max-width: 520px) {
  .vb-countdown-eleven-message {
    grid-template-columns: 1fr;
  }

  .vb-countdown-eleven-message strong {
    white-space: normal;
  }

  .vb-countdown-eleven-timer {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 340px) {
  .vb-countdown-eleven-timer {
    grid-template-columns: 1fr;
  }
}

This sticky top bar countdown timer is useful for ecommerce announcement bars, website-wide offers, free shipping campaigns, SaaS promotions, course launches, and limited-time header messages.

12. Hero Section Countdown Timer

A hero section countdown timer is useful for landing pages, product campaigns, startup launches, SaaS offers, event pages, course launches, and homepage promotions where the timer should be part of the main first-screen message.

This example uses a large hero layout with a split visual composition, layered gradient shapes, big headline, CTA buttons, trust badges, and a bold countdown panel. The JavaScript uses a repeating 10-day campaign cycle, so the timer keeps working without ending permanently.

Example 12

Hero Section Countdown Timer

Use this countdown timer inside a hero section for launches, landing pages, product campaigns, event pages, seasonal offers, and startup promotions.

Start Now
No credit card required Limited campaign Next cycle loading
Campaign ends in Countdown active
00 Days
00 Hours
00 Min
00 Sec

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twelve]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 10;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-03T12:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextDateEl = timer.querySelector("[data-next-date]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextCampaignEnd(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextEnd = getNextCampaignEnd(now);
    let remaining = nextEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextEnd).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextDateEl.textContent = "Ends " + dateText;
    statusEl.textContent = "Countdown active";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twelve-demo">
  <section class="vb-countdown-twelve-hero" data-vb-countdown-twelve data-cycle-days="10">
    <div class="vb-countdown-twelve-copy">
      <span class="vb-countdown-twelve-kicker">Example 12</span>
      <h3>Hero Section Countdown Timer</h3>
      <p>Use this countdown timer inside a hero section for launches, landing pages, product campaigns, event pages, seasonal offers, and startup promotions.</p>

      <div class="vb-countdown-twelve-actions">
        <a href="#hero-offer">Start Now</a>
        <button type="button">View Details</button>
      </div>

      <div class="vb-countdown-twelve-badges">
        <span>No credit card required</span>
        <span>Limited campaign</span>
        <span data-next-date>Next cycle loading</span>
      </div>
    </div>

    <div class="vb-countdown-twelve-card">
      <div class="vb-countdown-twelve-card-top">
        <span>Campaign ends in</span>
        <strong data-status>Countdown active</strong>
      </div>

      <div class="vb-countdown-twelve-timer" aria-label="Hero section countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>
    </div>
  </section>
</div>

CSS

.vb-countdown-twelve-demo,
.vb-countdown-twelve-demo * {
  box-sizing: border-box;
}

.vb-countdown-twelve-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 54px 18px 54px 18px;
  background:
    radial-gradient(circle at 16% 18%, rgba(16, 185, 129, 0.20), transparent 34%),
    radial-gradient(circle at 88% 22%, rgba(59, 130, 246, 0.20), transparent 34%),
    linear-gradient(135deg, #ecfdf5 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(16, 185, 129, 0.20);
  box-shadow: 0 28px 80px rgba(5, 150, 105, 0.10);
}

.vb-countdown-twelve-hero {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(280px, 0.72fr);
  gap: clamp(24px, 4vw, 42px);
  align-items: center;
  max-width: 1160px;
  min-height: 560px;
  margin: 0 auto;
  padding: clamp(24px, 5vw, 54px);
  overflow: hidden;
  border-radius: 44px 12px 44px 12px;
  background:
    radial-gradient(circle at 24% 22%, rgba(255,255,255,0.20), transparent 34%),
    linear-gradient(135deg, #052e2b 0%, #064e3b 48%, #1d4ed8 100%) !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.24);
}

.vb-countdown-twelve-hero::before {
  content: "";
  position: absolute;
  right: -140px;
  top: -160px;
  width: 390px;
  height: 390px;
  border-radius: 44% 56% 32% 68%;
  background: rgba(125, 211, 252, 0.18);
}

.vb-countdown-twelve-hero::after {
  content: "";
  position: absolute;
  left: 44%;
  bottom: -160px;
  width: 320px;
  height: 320px;
  border-radius: 68% 32% 58% 42%;
  background: rgba(52, 211, 153, 0.16);
}

.vb-countdown-twelve-copy,
.vb-countdown-twelve-card {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-countdown-twelve-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px 999px 999px 4px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twelve-copy h3 {
  max-width: 820px;
  margin: 0 0 18px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(38px, 6.4vw, 88px) !important;
  line-height: 0.86 !important;
  font-weight: 950 !important;
  letter-spacing: -0.085em;
  overflow-wrap: anywhere;
}

.vb-countdown-twelve-copy p {
  max-width: 690px;
  margin: 0 0 24px !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: clamp(15px, 1.8vw, 17px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twelve-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 20px;
}

.vb-countdown-twelve-actions a,
.vb-countdown-twelve-actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 14px 20px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
}

.vb-countdown-twelve-actions a {
  background: #ffffff;
  color: #065f46 !important;
  -webkit-text-fill-color: #065f46 !important;
  text-decoration: none !important;
  box-shadow: 0 16px 34px rgba(15, 23, 42, 0.18);
}

.vb-countdown-twelve-actions button {
  border: 1px solid rgba(255,255,255,0.20);
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-countdown-twelve-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.vb-countdown-twelve-badges span {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.12);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-twelve-card {
  padding: clamp(20px, 3vw, 28px);
  border-radius: 18px 42px 18px 42px;
  background:
    radial-gradient(circle at 22% 16%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, rgba(255,255,255,0.16), rgba(255,255,255,0.055)) !important;
  border: 1px solid rgba(255,255,255,0.16);
  backdrop-filter: blur(14px);
  box-shadow: 0 28px 80px rgba(2, 6, 23, 0.22);
}

.vb-countdown-twelve-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 18px;
}

.vb-countdown-twelve-card-top span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-twelve-card-top strong {
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 13px;
  font-weight: 900;
  text-align: right;
}

.vb-countdown-twelve-timer {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.vb-countdown-twelve-timer div {
  min-width: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  align-items: center;
  padding: 14px 16px;
  border-radius: 16px 28px 16px 28px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-countdown-twelve-timer strong {
  min-width: 72px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 6vw, 52px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twelve-timer span {
  color: #d1fae5 !important;
  -webkit-text-fill-color: #d1fae5 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

@media (max-width: 920px) {
  .vb-countdown-twelve-hero {
    grid-template-columns: 1fr;
    min-height: auto;
  }

  .vb-countdown-twelve-timer {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .vb-countdown-twelve-timer div {
    grid-template-columns: 1fr;
    place-items: center;
    text-align: center;
  }

  .vb-countdown-twelve-timer strong {
    min-width: 0;
    font-size: clamp(28px, 6vw, 44px);
  }
}

@media (max-width: 640px) {
  .vb-countdown-twelve-demo {
    border-radius: 34px 12px 34px 12px;
  }

  .vb-countdown-twelve-hero {
    border-radius: 28px 10px 28px 10px;
  }

  .vb-countdown-twelve-copy h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twelve-actions a,
  .vb-countdown-twelve-actions button {
    width: 100%;
  }

  .vb-countdown-twelve-card-top {
    flex-direction: column;
    align-items: flex-start;
  }

  .vb-countdown-twelve-card-top strong {
    text-align: left;
  }

  .vb-countdown-twelve-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-twelve-timer strong {
    font-size: clamp(30px, 13vw, 46px);
  }
}

@media (max-width: 360px) {
  .vb-countdown-twelve-timer {
    grid-template-columns: 1fr;
  }
}

This hero section countdown timer is useful for landing pages, SaaS launches, product campaigns, event pages, course launch sections, homepage promotions, and startup offer pages.

13. Minimal Inline Countdown Timer

A minimal inline countdown timer is useful for pricing pages, product pages, checkout messages, small promo banners, course enrollment notices, blog CTA sections, and compact website areas where a full countdown block would be too large.

This example uses a clean inline layout with a short message, compact timer chips, subtle border styling, and a simple call-to-action link. The JavaScript uses a repeating 3-hour offer window, so the demo keeps working and never stays stopped at zero.

Example 13 Minimal Inline Countdown Timer

Compact countdown for small promo messages, pricing sections, checkout notices, and lightweight website CTAs.

00 Hrs
00 Min
00 Sec
Get Offer

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-thirteen]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 10800;
  let remaining = duration;

  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const hours = Math.floor(remaining / 3600);
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);

    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-thirteen-demo">
  <div class="vb-countdown-thirteen-inline" data-vb-countdown-thirteen data-duration="10800">
    <div class="vb-countdown-thirteen-copy">
      <span>Example 13</span>
      <strong>Minimal Inline Countdown Timer</strong>
      <p>Compact countdown for small promo messages, pricing sections, checkout notices, and lightweight website CTAs.</p>
    </div>

    <div class="vb-countdown-thirteen-timer" aria-label="Minimal inline countdown timer">
      <div>
        <strong data-hours>00</strong>
        <span>Hrs</span>
      </div>

      <div>
        <strong data-minutes>00</strong>
        <span>Min</span>
      </div>

      <div>
        <strong data-seconds>00</strong>
        <span>Sec</span>
      </div>
    </div>

    <a href="#inline-offer">Get Offer</a>
  </div>
</div>

CSS

.vb-countdown-thirteen-demo,
.vb-countdown-thirteen-demo * {
  box-sizing: border-box;
}

.vb-countdown-thirteen-demo {
  margin: 28px 0;
  padding: clamp(16px, 3vw, 28px);
  border-radius: 22px;
  background:
    linear-gradient(135deg, #ffffff 0%, #f8fafc 52%, #f1f5f9 100%) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.08);
}

.vb-countdown-thirteen-inline {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: clamp(14px, 2vw, 22px);
  align-items: center;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(16px, 2.2vw, 22px);
  border-radius: 18px;
  background: #ffffff;
  border: 1px solid rgba(203, 213, 225, 0.9);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.9);
}

.vb-countdown-thirteen-copy {
  min-width: 0;
  display: grid;
  gap: 5px;
}

.vb-countdown-thirteen-copy span {
  display: inline-flex;
  width: fit-content;
  padding: 5px 9px;
  border-radius: 999px;
  background: #eef2ff;
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-thirteen-copy strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(18px, 3vw, 28px);
  line-height: 1.1;
  font-weight: 950;
  letter-spacing: -0.045em;
  overflow-wrap: anywhere;
}

.vb-countdown-thirteen-copy p {
  max-width: 680px;
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

.vb-countdown-thirteen-timer {
  display: grid;
  grid-template-columns: repeat(3, minmax(56px, 1fr));
  gap: 8px;
  min-width: 0;
}

.vb-countdown-thirteen-timer div {
  min-width: 0;
  padding: 10px 8px;
  border-radius: 14px;
  background: #0f172a;
  text-align: center;
}

.vb-countdown-thirteen-timer strong {
  display: block;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(20px, 3vw, 30px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
}

.vb-countdown-thirteen-timer span {
  display: block;
  margin-top: 4px;
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  font-size: 9px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-thirteen-inline a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 12px 16px;
  border-radius: 14px;
  background: #4f46e5;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 13px;
  font-weight: 950;
  white-space: nowrap;
  box-shadow: 0 14px 28px rgba(79, 70, 229, 0.20);
}

@media (max-width: 860px) {
  .vb-countdown-thirteen-inline {
    grid-template-columns: 1fr;
  }

  .vb-countdown-thirteen-inline a {
    width: 100%;
  }
}

@media (max-width: 420px) {
  .vb-countdown-thirteen-timer {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .vb-countdown-thirteen-copy strong {
    font-size: 22px;
  }
}

@media (max-width: 320px) {
  .vb-countdown-thirteen-timer {
    grid-template-columns: 1fr;
  }
}

This minimal inline countdown timer is useful for compact promo rows, checkout reminders, product page notices, pricing page offers, course enrollment CTAs, and small website announcement sections.

14. Flip Clock Countdown Timer

A flip clock countdown timer is useful for launch pages, event pages, ecommerce campaigns, coming soon websites, product drops, conference pages, and premium landing pages that need a more visual timer style.

This example uses a retro flip-clock inspired design with split number cards, a dark stage background, metallic borders, and a centered campaign layout. The JavaScript uses a repeating 12-day cycle, so the countdown continues working instead of staying expired.

Example 14

Flip Clock Countdown Timer

Use this flip-style countdown timer for premium launches, event pages, coming soon campaigns, product drops, and visual landing page sections.

00 Days
00 Hours
00 Minutes
00 Seconds

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-fourteen]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 12;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-04T08:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextCycleEnd(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextEnd = getNextCycleEnd(now);
    let remaining = nextEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Launch countdown is active.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-fourteen-demo">
  <section class="vb-countdown-fourteen-stage" data-vb-countdown-fourteen data-cycle-days="12">
    <div class="vb-countdown-fourteen-header">
      <span>Example 14</span>
      <h3>Flip Clock Countdown Timer</h3>
      <p>Use this flip-style countdown timer for premium launches, event pages, coming soon campaigns, product drops, and visual landing page sections.</p>
    </div>

    <div class="vb-countdown-fourteen-clock" aria-label="Flip clock countdown timer">
      <div class="vb-countdown-fourteen-unit">
        <strong data-days>00</strong>
        <span>Days</span>
      </div>

      <div class="vb-countdown-fourteen-unit">
        <strong data-hours>00</strong>
        <span>Hours</span>
      </div>

      <div class="vb-countdown-fourteen-unit">
        <strong data-minutes>00</strong>
        <span>Minutes</span>
      </div>

      <div class="vb-countdown-fourteen-unit">
        <strong data-seconds>00</strong>
        <span>Seconds</span>
      </div>
    </div>

    <div class="vb-countdown-fourteen-footer">
      <p data-status>Launch countdown is active.</p>
      <a href="#flip-clock-launch">Join Launch List</a>
    </div>
  </section>
</div>

CSS

.vb-countdown-fourteen-demo,
.vb-countdown-fourteen-demo * {
  box-sizing: border-box;
}

.vb-countdown-fourteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 18px;
  background:
    radial-gradient(circle at 18% 18%, rgba(234, 179, 8, 0.18), transparent 34%),
    radial-gradient(circle at 86% 18%, rgba(148, 163, 184, 0.16), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #fefce8 52%, #ffffff 100%) !important;
  border: 1px solid rgba(234, 179, 8, 0.22);
  box-shadow: 0 28px 80px rgba(113, 63, 18, 0.10);
}

.vb-countdown-fourteen-stage {
  position: relative;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(24px, 5vw, 50px);
  overflow: hidden;
  border-radius: 12px 42px 12px 42px;
  background:
    linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.04) 1px, transparent 1px),
    linear-gradient(135deg, #020617 0%, #111827 54%, #1f2937 100%) !important;
  background-size: 34px 34px, 34px 34px, auto !important;
  border: 1px solid rgba(255,255,255,0.10);
  box-shadow: 0 30px 90px rgba(2, 6, 23, 0.36);
}

.vb-countdown-fourteen-stage::before {
  content: "";
  position: absolute;
  left: -100px;
  top: -120px;
  width: 280px;
  height: 280px;
  border-radius: 999px;
  background: rgba(234, 179, 8, 0.15);
  filter: blur(2px);
}

.vb-countdown-fourteen-stage::after {
  content: "";
  position: absolute;
  right: -120px;
  bottom: -120px;
  width: 320px;
  height: 320px;
  border-radius: 999px;
  background: rgba(59, 130, 246, 0.12);
  filter: blur(2px);
}

.vb-countdown-fourteen-header,
.vb-countdown-fourteen-clock,
.vb-countdown-fourteen-footer {
  position: relative;
  z-index: 2;
}

.vb-countdown-fourteen-header {
  max-width: 820px;
  margin: 0 auto 28px;
  text-align: center;
}

.vb-countdown-fourteen-header span {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 8px;
  background: rgba(234, 179, 8, 0.14);
  border: 1px solid rgba(250, 204, 21, 0.22);
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.vb-countdown-fourteen-header h3 {
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 78px) !important;
  line-height: 0.9 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-fourteen-header p {
  max-width: 720px;
  margin: 0 auto !important;
  color: #d1d5db !important;
  -webkit-text-fill-color: #d1d5db !important;
  font-size: clamp(15px, 1.8vw, 17px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-fourteen-clock {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(10px, 2vw, 18px);
  margin: 0 auto 26px;
}

.vb-countdown-fourteen-unit {
  min-width: 0;
  display: grid;
  gap: 10px;
  text-align: center;
}

.vb-countdown-fourteen-unit strong {
  position: relative;
  display: grid;
  place-items: center;
  min-height: clamp(92px, 12vw, 145px);
  padding: 14px 8px;
  overflow: hidden;
  border-radius: 12px;
  background:
    linear-gradient(to bottom, #111827 0%, #111827 49%, #020617 50%, #020617 100%) !important;
  border: 1px solid rgba(255,255,255,0.12);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(38px, 7vw, 76px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.065em;
  font-variant-numeric: tabular-nums;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.09),
    0 20px 46px rgba(0,0,0,0.28);
}

.vb-countdown-fourteen-unit strong::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: rgba(255,255,255,0.16);
}

.vb-countdown-fourteen-unit strong::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  top: 10px;
  height: 28%;
  border-radius: 8px;
  background: linear-gradient(to bottom, rgba(255,255,255,0.08), transparent);
}

.vb-countdown-fourteen-unit span {
  display: inline-flex;
  justify-content: center;
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-fourteen-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  flex-wrap: wrap;
}

.vb-countdown-fourteen-footer p {
  margin: 0 !important;
  color: #e5e7eb !important;
  -webkit-text-fill-color: #e5e7eb !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-countdown-fourteen-footer a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 10px;
  background: linear-gradient(135deg, #eab308, #f97316);
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(234, 179, 8, 0.22);
}

@media (max-width: 820px) {
  .vb-countdown-fourteen-clock {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-fourteen-unit strong {
    font-size: clamp(44px, 13vw, 72px);
  }
}

@media (max-width: 640px) {
  .vb-countdown-fourteen-stage {
    border-radius: 10px 28px 10px 28px;
  }

  .vb-countdown-fourteen-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-fourteen-footer a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-fourteen-clock {
    grid-template-columns: 1fr;
  }
}

This flip clock countdown timer is useful for launch pages, event landing pages, coming soon websites, premium ecommerce campaigns, digital product drops, conference pages, and visual promotional sections.

15. Card Grid Countdown Timer

A card grid countdown timer is useful for product grids, course grids, event listings, deal sections, marketplace offers, pricing blocks, and landing pages where several countdown cards need to sit in a clean responsive layout.

This example uses a modern card grid layout with three promotional cards, a main countdown card, soft shadows, rounded product blocks, and responsive behavior for tablet and mobile screens. The JavaScript uses a repeating 5-day cycle, so the demo keeps working over time.

Example 15

Card Grid Countdown Timer

Use this countdown card grid for product offers, course launches, marketplace deals, event cards, and landing page promotion blocks.

00 Days
00 Hours
00 Min
00 Sec
View Campaign
Product Deal Save 25%

Limited card offer for featured products.

Course Offer Early Access

Enrollment window closes soon.

Campaign active Next cycle loading

The timer stays active with a repeating cycle.

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-fifteen]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 5;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-02T10:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextCycleEl = timer.querySelector("[data-next-cycle]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextCycleEnd(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextEnd = getNextCycleEnd(now);
    let remaining = nextEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextEnd).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextCycleEl.textContent = "Ends " + dateText;
    statusEl.textContent = "Campaign active";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-fifteen-demo">
  <section class="vb-countdown-fifteen-grid" data-vb-countdown-fifteen data-cycle-days="5">
    <div class="vb-countdown-fifteen-card vb-countdown-fifteen-main">
      <span class="vb-countdown-fifteen-kicker">Example 15</span>
      <h3>Card Grid Countdown Timer</h3>
      <p>Use this countdown card grid for product offers, course launches, marketplace deals, event cards, and landing page promotion blocks.</p>

      <div class="vb-countdown-fifteen-timer" aria-label="Card grid countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <a href="#grid-offer">View Campaign</a>
    </div>

    <div class="vb-countdown-fifteen-card vb-countdown-fifteen-small">
      <span>Product Deal</span>
      <strong>Save 25%</strong>
      <p>Limited card offer for featured products.</p>
    </div>

    <div class="vb-countdown-fifteen-card vb-countdown-fifteen-small">
      <span>Course Offer</span>
      <strong>Early Access</strong>
      <p>Enrollment window closes soon.</p>
    </div>

    <div class="vb-countdown-fifteen-card vb-countdown-fifteen-small">
      <span data-status>Campaign active</span>
      <strong data-next-cycle>Next cycle loading</strong>
      <p>The timer stays active with a repeating cycle.</p>
    </div>
  </section>
</div>

CSS

.vb-countdown-fifteen-demo,
.vb-countdown-fifteen-demo * {
  box-sizing: border-box;
}

.vb-countdown-fifteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 40px;
  background:
    radial-gradient(circle at 15% 20%, rgba(99, 102, 241, 0.15), transparent 34%),
    radial-gradient(circle at 85% 18%, rgba(236, 72, 153, 0.13), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #fdf2f8 54%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.18);
  box-shadow: 0 28px 80px rgba(49, 46, 129, 0.09);
}

.vb-countdown-fifteen-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(220px, 0.65fr);
  grid-template-rows: repeat(3, minmax(0, 1fr));
  gap: clamp(14px, 2vw, 18px);
  max-width: 1120px;
  margin: 0 auto;
}

.vb-countdown-fifteen-card {
  min-width: 0;
  border: 1px solid rgba(148, 163, 184, 0.22);
  background: #ffffff;
  box-shadow: 0 20px 60px rgba(15, 23, 42, 0.08);
}

.vb-countdown-fifteen-main {
  grid-row: 1 / 4;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(26px, 5vw, 48px);
  border-radius: 34px 12px 34px 12px;
  background:
    radial-gradient(circle at 20% 18%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #312e81 0%, #4f46e5 50%, #be185d 100%) !important;
}

.vb-countdown-fifteen-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-fifteen-main h3 {
  max-width: 760px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 80px) !important;
  line-height: 0.88 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-fifteen-main p {
  max-width: 660px;
  margin: 0 0 24px !important;
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: clamp(15px, 1.8vw, 17px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-fifteen-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 12px);
  margin-bottom: 22px;
}

.vb-countdown-fifteen-timer div {
  min-width: 0;
  padding: clamp(15px, 2.4vw, 22px) 8px;
  border-radius: 20px 20px 6px 20px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.16);
  text-align: center;
}

.vb-countdown-fifteen-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 4.7vw, 54px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-fifteen-timer span {
  display: block;
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-fifteen-main a {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 14px 20px;
  border-radius: 999px;
  background: #ffffff;
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(15, 23, 42, 0.20);
}

.vb-countdown-fifteen-small {
  display: grid;
  align-content: center;
  gap: 8px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 10px 28px 10px 28px;
}

.vb-countdown-fifteen-small:nth-child(2) {
  background: #fff7ed;
}

.vb-countdown-fifteen-small:nth-child(3) {
  background: #ecfdf5;
}

.vb-countdown-fifteen-small:nth-child(4) {
  background: #eef2ff;
}

.vb-countdown-fifteen-small span {
  display: inline-flex;
  width: fit-content;
  padding: 7px 10px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.06);
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-fifteen-small strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(24px, 4vw, 34px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.055em;
  overflow-wrap: anywhere;
}

.vb-countdown-fifteen-small p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
}

@media (max-width: 900px) {
  .vb-countdown-fifteen-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
  }

  .vb-countdown-fifteen-main {
    grid-row: auto;
  }
}

@media (max-width: 640px) {
  .vb-countdown-fifteen-main h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-fifteen-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-fifteen-timer strong {
    font-size: clamp(30px, 13vw, 48px);
  }

  .vb-countdown-fifteen-main a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-fifteen-timer {
    grid-template-columns: 1fr;
  }
}

This card grid countdown timer is useful for ecommerce deal grids, event cards, course launch cards, marketplace promotions, product offer sections, and responsive landing page campaign blocks.

16. Coupon Expiry Countdown Timer

A coupon expiry countdown timer is useful for discount codes, ecommerce checkout pages, popup offers, coupon landing pages, email campaign pages, cart recovery pages, and promotional banners where visitors need to use a coupon before it expires.

This example uses a coupon-style layout with dashed borders, a tear-off edge, coupon code block, timer strip, and clear call-to-action button. The JavaScript uses a repeating 8-hour coupon window, so the demo stays active and never remains stopped at zero.

Example 16

Coupon Expiry Countdown Timer

Use this countdown timer for coupon codes, checkout discounts, popup offers, cart recovery campaigns, and promotional landing pages.

Coupon Code SAVE30
30% OFF

Coupon expires soon.

00 Hours
00 Min
00 Sec
Use Coupon

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-sixteen]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 28800;
  let remaining = duration;

  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const hours = Math.floor(remaining / 3600);
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Coupon expires soon.";

    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-sixteen-demo">
  <section class="vb-countdown-sixteen-coupon" data-vb-countdown-sixteen data-duration="28800">
    <div class="vb-countdown-sixteen-left">
      <span class="vb-countdown-sixteen-kicker">Example 16</span>
      <h3>Coupon Expiry Countdown Timer</h3>
      <p>Use this countdown timer for coupon codes, checkout discounts, popup offers, cart recovery campaigns, and promotional landing pages.</p>

      <div class="vb-countdown-sixteen-code">
        <span>Coupon Code</span>
        <strong>SAVE30</strong>
      </div>
    </div>

    <div class="vb-countdown-sixteen-divider"></div>

    <div class="vb-countdown-sixteen-right">
      <strong class="vb-countdown-sixteen-offer">30% OFF</strong>
      <p data-status>Coupon expires soon.</p>

      <div class="vb-countdown-sixteen-timer" aria-label="Coupon expiry countdown timer">
        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <a href="#coupon-offer">Use Coupon</a>
    </div>
  </section>
</div>

CSS

.vb-countdown-sixteen-demo,
.vb-countdown-sixteen-demo * {
  box-sizing: border-box;
}

.vb-countdown-sixteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 26px;
  background:
    radial-gradient(circle at 15% 20%, rgba(245, 158, 11, 0.16), transparent 34%),
    radial-gradient(circle at 85% 20%, rgba(244, 63, 94, 0.14), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #fff1f2 52%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.22);
  box-shadow: 0 28px 80px rgba(120, 53, 15, 0.09);
}

.vb-countdown-sixteen-coupon {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 26px minmax(250px, 360px);
  gap: clamp(16px, 2.6vw, 24px);
  align-items: stretch;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(18px, 3vw, 26px);
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 2px dashed rgba(245, 158, 11, 0.42);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-countdown-sixteen-coupon::before,
.vb-countdown-sixteen-coupon::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 42px;
  height: 42px;
  border-radius: 999px;
  background: #fffbeb;
  border: 1px solid rgba(245, 158, 11, 0.20);
  transform: translateY(-50%);
  z-index: 3;
}

.vb-countdown-sixteen-coupon::before {
  left: -22px;
}

.vb-countdown-sixteen-coupon::after {
  right: -22px;
}

.vb-countdown-sixteen-left,
.vb-countdown-sixteen-right {
  min-width: 0;
}

.vb-countdown-sixteen-left {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 28px);
}

.vb-countdown-sixteen-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px 999px 4px 999px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-sixteen-left h3 {
  max-width: 740px;
  margin: 0 0 16px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 5.8vw, 76px) !important;
  line-height: 0.9 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-sixteen-left p {
  max-width: 650px;
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.72;
  font-weight: 650;
}

.vb-countdown-sixteen-code {
  display: inline-grid;
  gap: 6px;
  align-self: flex-start;
  max-width: 100%;
  padding: 16px 20px;
  border-radius: 18px 6px 18px 6px;
  background: #0f172a;
  box-shadow: 0 16px 34px rgba(15, 23, 42, 0.16);
}

.vb-countdown-sixteen-code span {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-sixteen-code strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 5vw, 42px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: 0.08em;
}

.vb-countdown-sixteen-divider {
  position: relative;
  min-height: 100%;
  border-left: 2px dashed rgba(245, 158, 11, 0.40);
}

.vb-countdown-sixteen-divider::before,
.vb-countdown-sixteen-divider::after {
  content: "";
  position: absolute;
  left: -14px;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  background: #fffbeb;
  border: 1px solid rgba(245, 158, 11, 0.20);
}

.vb-countdown-sixteen-divider::before {
  top: -28px;
}

.vb-countdown-sixteen-divider::after {
  bottom: -28px;
}

.vb-countdown-sixteen-right {
  display: grid;
  align-content: center;
  gap: 16px;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 22px;
  background:
    radial-gradient(circle at 20% 18%, rgba(255,255,255,0.20), transparent 34%),
    linear-gradient(135deg, #f97316 0%, #e11d48 100%) !important;
  color: #ffffff;
}

.vb-countdown-sixteen-offer {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(42px, 7vw, 68px);
  line-height: 0.88;
  font-weight: 950;
  letter-spacing: -0.08em;
}

.vb-countdown-sixteen-right p {
  margin: 0 !important;
  color: #fff7ed !important;
  -webkit-text-fill-color: #fff7ed !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 750;
}

.vb-countdown-sixteen-timer {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 9px;
}

.vb-countdown-sixteen-timer div {
  min-width: 0;
  padding: 14px 7px;
  border-radius: 16px 6px 16px 6px;
  background: rgba(255,255,255,0.16);
  border: 1px solid rgba(255,255,255,0.18);
  text-align: center;
}

.vb-countdown-sixteen-timer strong {
  display: block;
  margin-bottom: 5px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(26px, 5vw, 40px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
}

.vb-countdown-sixteen-timer span {
  display: block;
  color: #ffedd5 !important;
  -webkit-text-fill-color: #ffedd5 !important;
  font-size: 9px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-sixteen-right a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 999px;
  background: #ffffff;
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(15, 23, 42, 0.18);
}

@media (max-width: 900px) {
  .vb-countdown-sixteen-coupon {
    grid-template-columns: 1fr;
  }

  .vb-countdown-sixteen-divider {
    min-height: 0;
    height: 2px;
    border-left: 0;
    border-top: 2px dashed rgba(245, 158, 11, 0.40);
  }

  .vb-countdown-sixteen-divider::before,
  .vb-countdown-sixteen-divider::after {
    display: none;
  }
}

@media (max-width: 640px) {
  .vb-countdown-sixteen-left h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-sixteen-code {
    width: 100%;
  }

  .vb-countdown-sixteen-right a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-sixteen-timer {
    grid-template-columns: 1fr;
  }
}

This coupon expiry countdown timer is useful for ecommerce coupon codes, checkout discounts, popup offers, cart recovery campaigns, email campaign landing pages, and limited-time promotional banners.

17. Course Enrollment Countdown Timer

A course enrollment countdown timer is useful for online course pages, academy websites, coaching programs, learning platforms, cohort-based courses, early-bird enrollment campaigns, and training landing pages. It helps show when enrollment closes and encourages visitors to join before the deadline.

This example uses a clean course landing page style with a lesson preview card, enrollment badge, instructor-style information block, countdown modules, and a course CTA button. The JavaScript uses a repeating 14-day enrollment cycle, so the demo keeps working over time and never stays stopped at zero.

Example 17
12 modules Enrollment open
Online Course

Course Enrollment Countdown Timer

Use this countdown timer for online course launches, cohort-based programs, academy pages, training campaigns, and enrollment deadline sections.

Beginner friendly Certificate included Enrollment window loading
00 Days
00 Hours
00 Min
00 Sec

Enrollment is open for the next course window.

Enroll Now

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-seventeen]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 14;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-08T11:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextEnrollmentEl = timer.querySelector("[data-next-enrollment]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextEnrollmentEnd(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextEnd = getNextEnrollmentEnd(now);
    let remaining = nextEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextEnd).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextEnrollmentEl.textContent = "Enrollment closes " + dateText;
    statusEl.textContent = "Enrollment is open for the next course window.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-seventeen-demo">
  <section class="vb-countdown-seventeen-course" data-vb-countdown-seventeen data-cycle-days="14">
    <div class="vb-countdown-seventeen-preview">
      <span class="vb-countdown-seventeen-tag">Example 17</span>

      <div class="vb-countdown-seventeen-screen">
        <div class="vb-countdown-seventeen-screen-top">
          <i></i>
          <i></i>
          <i></i>
        </div>
        <div class="vb-countdown-seventeen-lines">
          <span></span>
          <span></span>
          <span></span>
        </div>
      </div>

      <div class="vb-countdown-seventeen-module">
        <strong>12 modules</strong>
        <span>Enrollment open</span>
      </div>
    </div>

    <div class="vb-countdown-seventeen-content">
      <span class="vb-countdown-seventeen-kicker">Online Course</span>
      <h3>Course Enrollment Countdown Timer</h3>
      <p>Use this countdown timer for online course launches, cohort-based programs, academy pages, training campaigns, and enrollment deadline sections.</p>

      <div class="vb-countdown-seventeen-meta">
        <span>Beginner friendly</span>
        <span>Certificate included</span>
        <span data-next-enrollment>Enrollment window loading</span>
      </div>

      <div class="vb-countdown-seventeen-timer" aria-label="Course enrollment countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <p class="vb-countdown-seventeen-status" data-status>Enrollment is open for the next course window.</p>

      <a href="#course-enrollment">Enroll Now</a>
    </div>
  </section>
</div>

CSS

.vb-countdown-seventeen-demo,
.vb-countdown-seventeen-demo * {
  box-sizing: border-box;
}

.vb-countdown-seventeen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 56px 18px 18px 56px;
  background:
    radial-gradient(circle at 14% 18%, rgba(59, 130, 246, 0.17), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(16, 185, 129, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfdf5 54%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.20);
  box-shadow: 0 28px 80px rgba(30, 64, 175, 0.10);
}

.vb-countdown-seventeen-course {
  display: grid;
  grid-template-columns: minmax(260px, 0.82fr) minmax(0, 1.18fr);
  gap: clamp(22px, 4vw, 38px);
  align-items: center;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 42px 12px 12px 42px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-countdown-seventeen-preview {
  position: relative;
  min-width: 0;
  min-height: clamp(330px, 42vw, 480px);
  overflow: hidden;
  border-radius: 34px 10px 10px 34px;
  background:
    radial-gradient(circle at 28% 20%, rgba(255,255,255,0.30), transparent 34%),
    linear-gradient(135deg, #1e3a8a 0%, #2563eb 52%, #10b981 100%) !important;
}

.vb-countdown-seventeen-tag {
  position: absolute;
  left: 18px;
  top: 18px;
  z-index: 4;
  display: inline-flex;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.16);
  border: 1px solid rgba(255,255,255,0.22);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-seventeen-screen {
  position: absolute;
  left: 50%;
  top: 48%;
  width: min(78%, 310px);
  min-height: 230px;
  transform: translate(-50%, -50%) rotate(-4deg);
  border-radius: 24px;
  background: rgba(255,255,255,0.92);
  box-shadow: 0 28px 76px rgba(15, 23, 42, 0.22);
  overflow: hidden;
}

.vb-countdown-seventeen-screen-top {
  display: flex;
  gap: 7px;
  padding: 14px;
  background: #0f172a;
}

.vb-countdown-seventeen-screen-top i {
  width: 10px;
  height: 10px;
  border-radius: 999px;
  background: #60a5fa;
}

.vb-countdown-seventeen-screen-top i:nth-child(2) {
  background: #34d399;
}

.vb-countdown-seventeen-screen-top i:nth-child(3) {
  background: #facc15;
}

.vb-countdown-seventeen-lines {
  display: grid;
  gap: 14px;
  padding: 24px;
}

.vb-countdown-seventeen-lines span {
  display: block;
  height: 18px;
  border-radius: 999px;
  background: #dbeafe;
}

.vb-countdown-seventeen-lines span:nth-child(1) {
  width: 84%;
}

.vb-countdown-seventeen-lines span:nth-child(2) {
  width: 62%;
  background: #bbf7d0;
}

.vb-countdown-seventeen-lines span:nth-child(3) {
  width: 74%;
  background: #e0e7ff;
}

.vb-countdown-seventeen-module {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: 24px;
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  padding: 16px;
  border-radius: 22px;
  background: rgba(15, 23, 42, 0.72);
  border: 1px solid rgba(255,255,255,0.14);
  backdrop-filter: blur(12px);
}

.vb-countdown-seventeen-module strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.04em;
}

.vb-countdown-seventeen-module span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-seventeen-content {
  min-width: 0;
  padding: clamp(10px, 2vw, 18px);
}

.vb-countdown-seventeen-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px 999px 999px 4px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-seventeen-content h3 {
  max-width: 760px;
  margin: 0 0 16px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5.8vw, 76px) !important;
  line-height: 0.9 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-seventeen-content p {
  max-width: 680px;
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-seventeen-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
}

.vb-countdown-seventeen-meta span {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: #f1f5f9;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-seventeen-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 12px);
  margin-bottom: 18px;
}

.vb-countdown-seventeen-timer div {
  min-width: 0;
  padding: clamp(15px, 2.4vw, 20px) 8px;
  border-radius: 10px 24px 10px 24px;
  background: #0f172a;
  text-align: center;
  box-shadow: 0 18px 42px rgba(15, 23, 42, 0.16);
}

.vb-countdown-seventeen-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 4.5vw, 50px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-seventeen-timer span {
  display: block;
  color: #93c5fd !important;
  -webkit-text-fill-color: #93c5fd !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-seventeen-status {
  margin-bottom: 18px !important;
}

.vb-countdown-seventeen-content a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 14px 20px;
  border-radius: 999px 999px 999px 8px;
  background: linear-gradient(135deg, #2563eb, #10b981);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(37, 99, 235, 0.22);
}

@media (max-width: 920px) {
  .vb-countdown-seventeen-course {
    grid-template-columns: 1fr;
  }

  .vb-countdown-seventeen-preview {
    border-radius: 30px 10px 10px 30px;
  }
}

@media (max-width: 640px) {
  .vb-countdown-seventeen-demo {
    border-radius: 34px 12px 12px 34px;
  }

  .vb-countdown-seventeen-course {
    border-radius: 28px 10px 10px 28px;
  }

  .vb-countdown-seventeen-content h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-seventeen-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-seventeen-timer strong {
    font-size: clamp(30px, 13vw, 46px);
  }

  .vb-countdown-seventeen-content a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-seventeen-timer {
    grid-template-columns: 1fr;
  }
}

This course enrollment countdown timer is useful for online course landing pages, academy websites, cohort launches, coaching programs, training pages, course discounts, and enrollment deadline sections.

18. Booking Deadline Countdown Timer

A booking deadline countdown timer is useful for hotel bookings, appointment pages, travel packages, event reservations, restaurant bookings, consultation pages, service businesses, and limited booking windows where visitors need to reserve before a deadline.

This example uses a booking-focused layout with a calendar-inspired side panel, deadline notice, availability cards, countdown display, and reservation button. The JavaScript uses a repeating 72-hour booking window, so the demo keeps working and never remains expired.

Example 18 -- Booking
Reservation Deadline

Booking Deadline Countdown Timer

Use this countdown timer for appointments, hotel bookings, travel deals, restaurant reservations, event bookings, and service-based landing pages.

6 slots Available today
24h Response time

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-eighteen]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 259200;
  let remaining = duration;

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");
  const calendarDayEl = timer.querySelector("[data-calendar-day]");
  const calendarMonthEl = timer.querySelector("[data-calendar-month]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCalendarPreview() {
    const deadline = new Date(Date.now() + remaining * 1000);
    calendarDayEl.textContent = formatNumber(deadline.getDate());
    calendarMonthEl.textContent = deadline.toLocaleString("en-US", { month: "short" });
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const days = Math.floor(remaining / 86400);
    const hours = Math.floor((remaining / 3600) % 24);
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Booking deadline active";

    updateCalendarPreview();
    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-eighteen-demo">
  <section class="vb-countdown-eighteen-booking" data-vb-countdown-eighteen data-duration="259200">
    <div class="vb-countdown-eighteen-calendar">
      <span>Example 18</span>
      <strong data-calendar-day>--</strong>
      <small data-calendar-month>Booking</small>
    </div>

    <div class="vb-countdown-eighteen-content">
      <div class="vb-countdown-eighteen-label">Reservation Deadline</div>
      <h3>Booking Deadline Countdown Timer</h3>
      <p>Use this countdown timer for appointments, hotel bookings, travel deals, restaurant reservations, event bookings, and service-based landing pages.</p>

      <div class="vb-countdown-eighteen-availability">
        <div>
          <strong>6 slots</strong>
          <span>Available today</span>
        </div>
        <div>
          <strong>24h</strong>
          <span>Response time</span>
        </div>
      </div>
    </div>

    <aside class="vb-countdown-eighteen-panel">
      <span data-status>Booking deadline active</span>

      <div class="vb-countdown-eighteen-timer" aria-label="Booking deadline countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <a href="#booking-deadline">Reserve Booking</a>
    </aside>
  </section>
</div>

CSS

.vb-countdown-eighteen-demo,
.vb-countdown-eighteen-demo * {
  box-sizing: border-box;
}

.vb-countdown-eighteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 14px 50px 50px 14px;
  background:
    radial-gradient(circle at 16% 20%, rgba(14, 165, 233, 0.16), transparent 34%),
    radial-gradient(circle at 88% 24%, rgba(245, 158, 11, 0.14), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #fffbeb 52%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.20);
  box-shadow: 0 28px 80px rgba(3, 105, 161, 0.09);
}

.vb-countdown-eighteen-booking {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr) minmax(260px, 360px);
  gap: clamp(18px, 3vw, 26px);
  align-items: stretch;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(14px, 2vw, 18px);
  border-radius: 10px 38px 38px 10px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-countdown-eighteen-calendar {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 10px;
  min-width: 0;
  min-height: 340px;
  border-radius: 8px 28px 28px 8px;
  background:
    radial-gradient(circle at 30% 20%, rgba(255,255,255,0.25), transparent 34%),
    linear-gradient(135deg, #0369a1 0%, #0ea5e9 52%, #f59e0b 100%) !important;
  color: #ffffff;
}

.vb-countdown-eighteen-calendar span {
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-eighteen-calendar strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(54px, 8vw, 88px);
  line-height: 0.85;
  font-weight: 950;
  letter-spacing: -0.07em;
  font-variant-numeric: tabular-nums;
}

.vb-countdown-eighteen-calendar small {
  color: #fff7ed !important;
  -webkit-text-fill-color: #fff7ed !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-countdown-eighteen-content {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  padding: clamp(18px, 3vw, 28px) 6px;
}

.vb-countdown-eighteen-label {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 8px 999px 999px 8px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-eighteen-content h3 {
  max-width: 740px;
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5.5vw, 70px) !important;
  line-height: 0.92 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-countdown-eighteen-content p {
  max-width: 680px;
  margin: 0 0 22px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.72;
  font-weight: 650;
}

.vb-countdown-eighteen-availability {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 180px));
  gap: 12px;
}

.vb-countdown-eighteen-availability div {
  min-width: 0;
  padding: 16px;
  border-radius: 20px 8px 20px 8px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.18);
}

.vb-countdown-eighteen-availability strong {
  display: block;
  margin-bottom: 6px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 28px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.055em;
}

.vb-countdown-eighteen-availability span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-eighteen-panel {
  display: grid;
  align-content: center;
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 3vw, 26px);
  border-radius: 32px;
  background:
    radial-gradient(circle at 24% 16%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #075985 54%, #0c4a6e 100%) !important;
  box-shadow: 0 24px 70px rgba(3, 105, 161, 0.18);
}

.vb-countdown-eighteen-panel > span {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-eighteen-timer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.vb-countdown-eighteen-timer div {
  min-width: 0;
  padding: 16px 8px;
  border-radius: 20px 6px 20px 6px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
  text-align: center;
}

.vb-countdown-eighteen-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 6vw, 48px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-eighteen-timer span {
  display: block;
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-eighteen-panel a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 999px;
  background: linear-gradient(135deg, #0ea5e9, #f59e0b);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(14, 165, 233, 0.22);
}

@media (max-width: 980px) {
  .vb-countdown-eighteen-booking {
    grid-template-columns: 130px minmax(0, 1fr);
  }

  .vb-countdown-eighteen-panel {
    grid-column: 1 / -1;
  }

  .vb-countdown-eighteen-timer {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 700px) {
  .vb-countdown-eighteen-booking {
    grid-template-columns: 1fr;
  }

  .vb-countdown-eighteen-calendar {
    min-height: 190px;
    border-radius: 8px 24px 24px 8px;
  }

  .vb-countdown-eighteen-content h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-eighteen-availability {
    grid-template-columns: 1fr;
  }

  .vb-countdown-eighteen-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-eighteen-panel a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-eighteen-timer {
    grid-template-columns: 1fr;
  }
}

This booking deadline countdown timer is useful for appointment booking pages, hotel deals, restaurant reservation sections, travel packages, event booking pages, service websites, and limited reservation campaigns.

19. Auction Countdown Timer

An auction countdown timer is useful for bidding pages, marketplace auctions, NFT-style listings, collectible sales, product auctions, ticket auctions, and limited bidding windows where visitors need to place a bid before time runs out.

This example uses a bidding-focused auction layout with a current bid panel, bidder activity cards, countdown blocks, and a strong bid CTA. The JavaScript uses a repeating 48-hour auction window, so the demo keeps working and never remains stopped at zero.

Example 19

Auction Countdown Timer

Use this countdown timer for auction listings, bidding pages, marketplace offers, collectible drops, limited product auctions, and high-value sale pages.

Current Bid €1,240 18 active bidders
Top Bidder Buyer #482
Bid Step €25
Live auction active Place your bid

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-nineteen]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 172800;
  let remaining = duration;

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const days = Math.floor(remaining / 86400);
    const hours = Math.floor((remaining / 3600) % 24);
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Live auction active";

    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-nineteen-demo">
  <section class="vb-countdown-nineteen-auction" data-vb-countdown-nineteen data-duration="172800">
    <div class="vb-countdown-nineteen-item">
      <span class="vb-countdown-nineteen-kicker">Example 19</span>
      <h3>Auction Countdown Timer</h3>
      <p>Use this countdown timer for auction listings, bidding pages, marketplace offers, collectible drops, limited product auctions, and high-value sale pages.</p>

      <div class="vb-countdown-nineteen-bid">
        <span>Current Bid</span>
        <strong>€1,240</strong>
        <small>18 active bidders</small>
      </div>
    </div>

    <div class="vb-countdown-nineteen-activity">
      <div>
        <span>Top Bidder</span>
        <strong>Buyer #482</strong>
      </div>
      <div>
        <span>Bid Step</span>
        <strong>€25</strong>
      </div>
      <div>
        <span data-status>Live auction active</span>
        <strong>Place your bid</strong>
      </div>
    </div>

    <aside class="vb-countdown-nineteen-panel">
      <strong class="vb-countdown-nineteen-panel-title">Auction ends in</strong>

      <div class="vb-countdown-nineteen-timer" aria-label="Auction countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <a href="#auction-bid">Place Bid</a>
    </aside>
  </section>
</div>

CSS

.vb-countdown-nineteen-demo,
.vb-countdown-nineteen-demo * {
  box-sizing: border-box;
}

.vb-countdown-nineteen-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 12px 54px 12px 54px;
  background:
    radial-gradient(circle at 14% 18%, rgba(168, 85, 247, 0.16), transparent 34%),
    radial-gradient(circle at 88% 24%, rgba(251, 191, 36, 0.15), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #fffbeb 54%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.20);
  box-shadow: 0 28px 80px rgba(88, 28, 135, 0.09);
}

.vb-countdown-nineteen-auction {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(220px, 300px) minmax(260px, 340px);
  gap: clamp(18px, 3vw, 26px);
  align-items: stretch;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(18px, 3vw, 26px);
  border-radius: 10px 42px 10px 42px;
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.12), transparent 34%),
    linear-gradient(135deg, #1e1b4b 0%, #3b0764 50%, #111827 100%) !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
}

.vb-countdown-nineteen-item,
.vb-countdown-nineteen-activity,
.vb-countdown-nineteen-panel {
  min-width: 0;
}

.vb-countdown-nineteen-item {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(18px, 3vw, 28px);
}

.vb-countdown-nineteen-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px 999px 4px 999px;
  background: rgba(251, 191, 36, 0.14);
  border: 1px solid rgba(253, 224, 71, 0.22);
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-nineteen-item h3 {
  max-width: 760px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 5.8vw, 78px) !important;
  line-height: 0.9 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-nineteen-item p {
  max-width: 680px;
  margin: 0 0 22px !important;
  color: #e9d5ff !important;
  -webkit-text-fill-color: #e9d5ff !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-nineteen-bid {
  display: inline-grid;
  gap: 5px;
  align-self: flex-start;
  padding: 18px 20px;
  border-radius: 24px 6px 24px 6px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-countdown-nineteen-bid span,
.vb-countdown-nineteen-bid small {
  color: #fde68a !important;
  -webkit-text-fill-color: #fde68a !important;
  font-size: 12px;
  font-weight: 900;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-nineteen-bid strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 48px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.06em;
}

.vb-countdown-nineteen-activity {
  display: grid;
  gap: 12px;
  align-content: center;
}

.vb-countdown-nineteen-activity div {
  padding: 18px;
  border-radius: 8px 26px 8px 26px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-countdown-nineteen-activity span {
  display: block;
  margin-bottom: 7px;
  color: #c4b5fd !important;
  -webkit-text-fill-color: #c4b5fd !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-nineteen-activity strong {
  display: block;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(20px, 3vw, 28px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-countdown-nineteen-panel {
  display: grid;
  align-content: center;
  gap: 16px;
  padding: clamp(20px, 3vw, 26px);
  border-radius: 36px 8px 36px 8px;
  background: #ffffff;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.20);
}

.vb-countdown-nineteen-panel-title {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 24px;
  line-height: 1.1;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-countdown-nineteen-timer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.vb-countdown-nineteen-timer div {
  min-width: 0;
  padding: 16px 8px;
  border-radius: 18px 6px 18px 6px;
  background: #f5f3ff;
  border: 1px solid rgba(168, 85, 247, 0.16);
  text-align: center;
}

.vb-countdown-nineteen-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 5px;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: clamp(30px, 6vw, 48px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-nineteen-timer span {
  display: block;
  color: #7c3aed !important;
  -webkit-text-fill-color: #7c3aed !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-nineteen-panel a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 999px;
  background: linear-gradient(135deg, #7c3aed, #f59e0b);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(124, 58, 237, 0.22);
}

@media (max-width: 1050px) {
  .vb-countdown-nineteen-auction {
    grid-template-columns: 1fr 1fr;
  }

  .vb-countdown-nineteen-panel {
    grid-column: 1 / -1;
  }

  .vb-countdown-nineteen-timer {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 700px) {
  .vb-countdown-nineteen-auction {
    grid-template-columns: 1fr;
  }

  .vb-countdown-nineteen-item h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-nineteen-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-nineteen-panel a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-nineteen-timer {
    grid-template-columns: 1fr;
  }
}

This auction countdown timer is useful for bidding pages, marketplace listings, product auctions, collectible sales, event ticket auctions, NFT-style drops, and limited bidding campaigns.

20. Donation Campaign Countdown Timer

A donation campaign countdown timer is useful for nonprofit campaigns, fundraising pages, charity events, community projects, donation drives, awareness campaigns, and goal-based landing pages where supporters need to donate before a campaign deadline.

This example uses a donation-focused campaign layout with a fundraising progress bar, goal cards, donor message area, deadline countdown, and donation CTA. The JavaScript uses a repeating 30-day campaign cycle, so the timer keeps working instead of remaining expired.

Example 20

Donation Campaign Countdown Timer

Use this countdown timer for nonprofit fundraising pages, charity campaigns, donation drives, awareness events, community projects, and goal-based campaign pages.

€18,450 raised Goal: €25,000
428 Supporters
74% Funded
Campaign active Deadline

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twenty]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 30;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-01T09:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextCampaignEl = timer.querySelector("[data-next-campaign]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextCampaignEnd(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextEnd = getNextCampaignEnd(now);
    let remaining = nextEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextEnd).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextCampaignEl.textContent = "Ends " + dateText;
    statusEl.textContent = "Donation window active";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twenty-demo">
  <section class="vb-countdown-twenty-donation" data-vb-countdown-twenty data-cycle-days="30">
    <div class="vb-countdown-twenty-content">
      <span class="vb-countdown-twenty-kicker">Example 20</span>
      <h3>Donation Campaign Countdown Timer</h3>
      <p>Use this countdown timer for nonprofit fundraising pages, charity campaigns, donation drives, awareness events, community projects, and goal-based campaign pages.</p>

      <div class="vb-countdown-twenty-progress">
        <div class="vb-countdown-twenty-progress-head">
          <strong>€18,450 raised</strong>
          <span>Goal: €25,000</span>
        </div>
        <div class="vb-countdown-twenty-progress-bar">
          <i></i>
        </div>
      </div>

      <div class="vb-countdown-twenty-stats">
        <div>
          <strong>428</strong>
          <span>Supporters</span>
        </div>
        <div>
          <strong>74%</strong>
          <span>Funded</span>
        </div>
        <div>
          <strong data-next-campaign>Campaign active</strong>
          <span>Deadline</span>
        </div>
      </div>
    </div>

    <aside class="vb-countdown-twenty-panel">
      <span data-status>Donation window active</span>

      <div class="vb-countdown-twenty-timer" aria-label="Donation campaign countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <p>Every contribution helps move this campaign closer to its goal before the current deadline.</p>
      <a href="#donate-now">Donate Now</a>
    </aside>
  </section>
</div>

CSS

.vb-countdown-twenty-demo,
.vb-countdown-twenty-demo * {
  box-sizing: border-box;
}

.vb-countdown-twenty-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 48px 12px 48px 12px;
  background:
    radial-gradient(circle at 14% 18%, rgba(20, 184, 166, 0.16), transparent 34%),
    radial-gradient(circle at 88% 22%, rgba(34, 197, 94, 0.14), transparent 34%),
    linear-gradient(135deg, #f0fdfa 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.20);
  box-shadow: 0 28px 80px rgba(15, 118, 110, 0.09);
}

.vb-countdown-twenty-donation {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(270px, 380px);
  gap: clamp(20px, 3vw, 30px);
  align-items: stretch;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 38px 10px 38px 10px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-countdown-twenty-content {
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.vb-countdown-twenty-kicker {
  display: inline-flex;
  align-self: flex-start;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px 999px 4px 999px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twenty-content h3 {
  max-width: 780px;
  margin: 0 0 16px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(36px, 5.8vw, 76px) !important;
  line-height: 0.9 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-twenty-content p {
  max-width: 720px;
  margin: 0 0 24px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twenty-progress {
  max-width: 720px;
  margin-bottom: 18px;
  padding: 18px;
  border-radius: 24px 8px 24px 8px;
  background: #f0fdfa;
  border: 1px solid rgba(20, 184, 166, 0.18);
}

.vb-countdown-twenty-progress-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.vb-countdown-twenty-progress-head strong {
  color: #134e4a !important;
  -webkit-text-fill-color: #134e4a !important;
  font-size: 18px;
  line-height: 1.2;
  font-weight: 950;
}

.vb-countdown-twenty-progress-head span {
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 13px;
  font-weight: 850;
}

.vb-countdown-twenty-progress-bar {
  overflow: hidden;
  height: 14px;
  border-radius: 999px;
  background: #ccfbf1;
}

.vb-countdown-twenty-progress-bar i {
  display: block;
  width: 74%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #14b8a6, #22c55e) !important;
}

.vb-countdown-twenty-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  max-width: 720px;
}

.vb-countdown-twenty-stats div {
  min-width: 0;
  padding: 16px;
  border-radius: 20px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.18);
}

.vb-countdown-twenty-stats strong {
  display: block;
  margin-bottom: 6px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(20px, 3vw, 30px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.055em;
  overflow-wrap: anywhere;
}

.vb-countdown-twenty-stats span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-twenty-panel {
  display: grid;
  align-content: center;
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 3vw, 26px);
  border-radius: 8px 34px 8px 34px;
  background:
    radial-gradient(circle at 24% 16%, rgba(255,255,255,0.15), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #134e4a 56%, #064e3b 100%) !important;
  box-shadow: 0 24px 70px rgba(15, 118, 110, 0.18);
}

.vb-countdown-twenty-panel > span {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(20, 184, 166, 0.14);
  border: 1px solid rgba(94, 234, 212, 0.20);
  color: #99f6e4 !important;
  -webkit-text-fill-color: #99f6e4 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-twenty-timer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.vb-countdown-twenty-timer div {
  min-width: 0;
  padding: 16px 8px;
  border-radius: 18px 6px 18px 6px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
  text-align: center;
}

.vb-countdown-twenty-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 6vw, 48px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twenty-timer span {
  display: block;
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-twenty-panel p {
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
}

.vb-countdown-twenty-panel a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 999px;
  background: linear-gradient(135deg, #14b8a6, #22c55e);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(20, 184, 166, 0.22);
}

@media (max-width: 920px) {
  .vb-countdown-twenty-donation {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twenty-timer {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .vb-countdown-twenty-content h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twenty-progress-head {
    flex-direction: column;
  }

  .vb-countdown-twenty-stats {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twenty-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-twenty-panel a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-twenty-timer {
    grid-template-columns: 1fr;
  }
}

This donation campaign countdown timer is useful for nonprofit fundraising pages, charity campaigns, donation drives, community projects, awareness events, goal-based landing pages, and campaign deadline sections.

21. Maintenance Mode Countdown Timer

A maintenance mode countdown timer is useful for websites, SaaS dashboards, web apps, hosting panels, admin areas, product platforms, and service pages that need to show when scheduled maintenance will end.

This example uses a technical maintenance layout with warning stripes, a system-status panel, uptime cards, terminal-style details, and a bold countdown display. The JavaScript uses a repeating 4-hour maintenance window, so the demo keeps working and never stays expired.

Example 21 Scheduled maintenance active

Maintenance Mode Countdown Timer

Use this countdown timer for scheduled maintenance pages, SaaS dashboards, hosting panels, app status pages, and service update screens.

System notice Maintenance window is currently running. Services will return after the countdown ends. Automatic demo cycle enabled.
00 Hours
00 Minutes
00 Seconds
99.9% Target uptime
Live Status monitor

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentyone]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 14400;
  let remaining = duration;

  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const hours = Math.floor(remaining / 3600);
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Scheduled maintenance active";

    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentyone-demo">
  <section class="vb-countdown-twentyone-maintenance" data-vb-countdown-twentyone data-duration="14400">
    <div class="vb-countdown-twentyone-header">
      <span class="vb-countdown-twentyone-kicker">Example 21</span>
      <strong data-status>Scheduled maintenance active</strong>
    </div>

    <div class="vb-countdown-twentyone-main">
      <div class="vb-countdown-twentyone-copy">
        <h3>Maintenance Mode Countdown Timer</h3>
        <p>Use this countdown timer for scheduled maintenance pages, SaaS dashboards, hosting panels, app status pages, and service update screens.</p>

        <div class="vb-countdown-twentyone-terminal">
          <span>System notice</span>
          <code>Maintenance window is currently running.</code>
          <code>Services will return after the countdown ends.</code>
          <code>Automatic demo cycle enabled.</code>
        </div>
      </div>

      <div class="vb-countdown-twentyone-panel">
        <div class="vb-countdown-twentyone-timer" aria-label="Maintenance mode countdown timer">
          <div>
            <strong data-hours>00</strong>
            <span>Hours</span>
          </div>

          <div>
            <strong data-minutes>00</strong>
            <span>Minutes</span>
          </div>

          <div>
            <strong data-seconds>00</strong>
            <span>Seconds</span>
          </div>
        </div>

        <div class="vb-countdown-twentyone-metrics">
          <div>
            <strong>99.9%</strong>
            <span>Target uptime</span>
          </div>

          <div>
            <strong>Live</strong>
            <span>Status monitor</span>
          </div>
        </div>
      </div>
    </div>
  </section>
</div>

CSS

.vb-countdown-twentyone-demo,
.vb-countdown-twentyone-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentyone-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 10px;
  background:
    repeating-linear-gradient(135deg, rgba(250, 204, 21, 0.16) 0 12px, rgba(15, 23, 42, 0.04) 12px 24px),
    linear-gradient(135deg, #f8fafc 0%, #fefce8 52%, #ffffff 100%) !important;
  border: 1px solid rgba(234, 179, 8, 0.26);
  box-shadow: 0 28px 80px rgba(113, 63, 18, 0.10);
}

.vb-countdown-twentyone-maintenance {
  max-width: 1160px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 8px;
  background:
    linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.04) 1px, transparent 1px),
    linear-gradient(135deg, #020617 0%, #111827 58%, #292524 100%) !important;
  background-size: 32px 32px, 32px 32px, auto !important;
  border: 1px solid rgba(255,255,255,0.12);
  box-shadow: 0 30px 90px rgba(2, 6, 23, 0.40);
}

.vb-countdown-twentyone-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  background:
    repeating-linear-gradient(135deg, #facc15 0 14px, #111827 14px 28px) !important;
}

.vb-countdown-twentyone-kicker {
  display: inline-flex;
  padding: 8px 12px;
  border-radius: 4px;
  background: #111827;
  color: #fef3c7 !important;
  -webkit-text-fill-color: #fef3c7 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentyone-header > strong {
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  background: #fef3c7;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 950;
  text-align: right;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-countdown-twentyone-main {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 380px);
  gap: clamp(22px, 4vw, 38px);
  align-items: center;
  padding: clamp(24px, 5vw, 48px);
}

.vb-countdown-twentyone-copy,
.vb-countdown-twentyone-panel {
  min-width: 0;
}

.vb-countdown-twentyone-copy h3 {
  max-width: 760px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 78px) !important;
  line-height: 0.88 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyone-copy p {
  max-width: 680px;
  margin: 0 0 24px !important;
  color: #d1d5db !important;
  -webkit-text-fill-color: #d1d5db !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twentyone-terminal {
  display: grid;
  gap: 8px;
  max-width: 680px;
  padding: 18px;
  border-radius: 6px;
  background: #020617;
  border: 1px solid rgba(250, 204, 21, 0.22);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
}

.vb-countdown-twentyone-terminal span {
  color: #facc15 !important;
  -webkit-text-fill-color: #facc15 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-twentyone-terminal code {
  display: block;
  white-space: normal;
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 13px;
  line-height: 1.5;
  font-weight: 750;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

.vb-countdown-twentyone-terminal code::before {
  content: "> ";
  color: #facc15;
  -webkit-text-fill-color: #facc15;
}

.vb-countdown-twentyone-panel {
  display: grid;
  gap: 16px;
  padding: clamp(18px, 3vw, 24px);
  border-radius: 8px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-countdown-twentyone-timer {
  display: grid;
  gap: 10px;
}

.vb-countdown-twentyone-timer div {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr);
  align-items: center;
  gap: 16px;
  min-width: 0;
  padding: 16px;
  border-radius: 4px;
  background: #facc15;
  border-left: 8px solid #111827;
}

.vb-countdown-twentyone-timer strong {
  color: #111827 !important;
  -webkit-text-fill-color: #111827 !important;
  font-size: clamp(34px, 6vw, 52px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyone-timer span {
  color: #292524 !important;
  -webkit-text-fill-color: #292524 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-twentyone-metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.vb-countdown-twentyone-metrics div {
  min-width: 0;
  padding: 16px;
  border-radius: 6px;
  background: #020617;
  border: 1px solid rgba(255,255,255,0.10);
}

.vb-countdown-twentyone-metrics strong {
  display: block;
  margin-bottom: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 22px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.04em;
}

.vb-countdown-twentyone-metrics span {
  color: #fef3c7 !important;
  -webkit-text-fill-color: #fef3c7 !important;
  font-size: 12px;
  font-weight: 850;
}

@media (max-width: 900px) {
  .vb-countdown-twentyone-main {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentyone-header {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-countdown-twentyone-header > strong {
    text-align: left;
  }
}

@media (max-width: 640px) {
  .vb-countdown-twentyone-copy h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twentyone-timer div {
    grid-template-columns: 1fr;
    gap: 8px;
  }

  .vb-countdown-twentyone-metrics {
    grid-template-columns: 1fr;
  }
}

This maintenance mode countdown timer is useful for website maintenance pages, SaaS status pages, hosting dashboards, update screens, app downtime notices, and scheduled service interruption messages.

22. App Download Launch Countdown Timer

An app download launch countdown timer is useful for mobile app landing pages, startup launches, app store campaigns, waitlist pages, beta launch pages, product preview pages, and app promotion sections.

This example uses a mobile app launch layout with a phone mockup, floating app cards, download buttons, launch badges, and a soft gradient countdown panel. The JavaScript uses a repeating 18-day launch cycle, so the timer keeps working far into the future.

A
Launch Soon
Users 24k
Example 22

App Download Launch Countdown Timer

Use this countdown timer for mobile app launch pages, beta release campaigns, app store waitlists, SaaS apps, startup products, and download landing pages.

iOS and Android Early access Next launch loading
00 Days
00 Hours
00 Min
00 Sec

App launch countdown is active.

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentytwo]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 18;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-06T10:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextLaunchEl = timer.querySelector("[data-next-launch]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextLaunch(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextLaunch = getNextLaunch(now);
    let remaining = nextLaunch - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextLaunch).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextLaunchEl.textContent = "Launch window: " + dateText;
    statusEl.textContent = "App launch countdown is active.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentytwo-demo">
  <section class="vb-countdown-twentytwo-app" data-vb-countdown-twentytwo data-cycle-days="18">
    <div class="vb-countdown-twentytwo-phone-wrap">
      <div class="vb-countdown-twentytwo-phone">
        <div class="vb-countdown-twentytwo-notch"></div>
        <div class="vb-countdown-twentytwo-appicon">A</div>
        <div class="vb-countdown-twentytwo-card vb-countdown-twentytwo-card-one">
          <span>Launch</span>
          <strong>Soon</strong>
        </div>
        <div class="vb-countdown-twentytwo-card vb-countdown-twentytwo-card-two">
          <span>Users</span>
          <strong>24k</strong>
        </div>
        <div class="vb-countdown-twentytwo-bars">
          <i></i>
          <i></i>
          <i></i>
        </div>
      </div>
    </div>

    <div class="vb-countdown-twentytwo-content">
      <span class="vb-countdown-twentytwo-kicker">Example 22</span>
      <h3>App Download Launch Countdown Timer</h3>
      <p>Use this countdown timer for mobile app launch pages, beta release campaigns, app store waitlists, SaaS apps, startup products, and download landing pages.</p>

      <div class="vb-countdown-twentytwo-badges">
        <span>iOS and Android</span>
        <span>Early access</span>
        <span data-next-launch>Next launch loading</span>
      </div>

      <div class="vb-countdown-twentytwo-timer" aria-label="App download launch countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <p class="vb-countdown-twentytwo-status" data-status>App launch countdown is active.</p>

      <div class="vb-countdown-twentytwo-buttons">
        <a href="#app-store">App Store</a>
        <a href="#google-play">Google Play</a>
      </div>
    </div>
  </section>
</div>

CSS

.vb-countdown-twentytwo-demo,
.vb-countdown-twentytwo-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentytwo-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 60px 60px 18px 18px;
  background:
    radial-gradient(circle at 18% 20%, rgba(236, 72, 153, 0.18), transparent 34%),
    radial-gradient(circle at 84% 20%, rgba(59, 130, 246, 0.18), transparent 34%),
    linear-gradient(135deg, #fdf2f8 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(236, 72, 153, 0.18);
  box-shadow: 0 28px 80px rgba(157, 23, 77, 0.09);
}

.vb-countdown-twentytwo-app {
  position: relative;
  display: grid;
  grid-template-columns: minmax(260px, 0.78fr) minmax(0, 1.22fr);
  gap: clamp(24px, 5vw, 46px);
  align-items: center;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(24px, 5vw, 52px);
  overflow: hidden;
  border-radius: 46px 46px 14px 14px;
  background:
    radial-gradient(circle at 20% 20%, rgba(255,255,255,0.22), transparent 34%),
    linear-gradient(135deg, #831843 0%, #4f46e5 52%, #0f172a 100%) !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.26);
}

.vb-countdown-twentytwo-app::before {
  content: "";
  position: absolute;
  right: -130px;
  top: -110px;
  width: 360px;
  height: 360px;
  border-radius: 44% 56% 64% 36%;
  background: rgba(147, 197, 253, 0.16);
}

.vb-countdown-twentytwo-app::after {
  content: "";
  position: absolute;
  left: 32%;
  bottom: -140px;
  width: 300px;
  height: 300px;
  border-radius: 70% 30% 48% 52%;
  background: rgba(244, 114, 182, 0.14);
}

.vb-countdown-twentytwo-phone-wrap,
.vb-countdown-twentytwo-content {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-countdown-twentytwo-phone-wrap {
  display: grid;
  place-items: center;
}

.vb-countdown-twentytwo-phone {
  position: relative;
  width: min(100%, 300px);
  aspect-ratio: 0.56 / 1;
  border-radius: 38px;
  background:
    radial-gradient(circle at 50% 16%, rgba(255,255,255,0.18), transparent 28%),
    linear-gradient(160deg, #020617 0%, #111827 48%, #312e81 100%) !important;
  border: 10px solid rgba(255,255,255,0.18);
  box-shadow:
    0 34px 90px rgba(2, 6, 23, 0.40),
    inset 0 1px 0 rgba(255,255,255,0.10);
  transform: rotate(-4deg);
}

.vb-countdown-twentytwo-notch {
  position: absolute;
  left: 50%;
  top: 12px;
  width: 96px;
  height: 24px;
  border-radius: 999px;
  background: #020617;
  transform: translateX(-50%);
}

.vb-countdown-twentytwo-appicon {
  position: absolute;
  left: 50%;
  top: 70px;
  display: grid;
  place-items: center;
  width: 84px;
  height: 84px;
  border-radius: 24px;
  transform: translateX(-50%);
  background: linear-gradient(135deg, #ec4899, #3b82f6);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 42px;
  font-weight: 950;
  box-shadow: 0 20px 44px rgba(236, 72, 153, 0.26);
}

.vb-countdown-twentytwo-card {
  position: absolute;
  display: grid;
  gap: 4px;
  padding: 14px;
  border-radius: 18px;
  background: rgba(255,255,255,0.11);
  border: 1px solid rgba(255,255,255,0.14);
  backdrop-filter: blur(10px);
}

.vb-countdown-twentytwo-card-one {
  left: 22px;
  right: 22px;
  top: 180px;
}

.vb-countdown-twentytwo-card-two {
  left: 22px;
  right: 22px;
  bottom: 88px;
}

.vb-countdown-twentytwo-card span {
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-twentytwo-card strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 28px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-countdown-twentytwo-bars {
  position: absolute;
  left: 22px;
  right: 22px;
  bottom: 32px;
  display: grid;
  gap: 8px;
}

.vb-countdown-twentytwo-bars i {
  display: block;
  height: 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.16);
}

.vb-countdown-twentytwo-bars i:nth-child(1) {
  width: 86%;
}

.vb-countdown-twentytwo-bars i:nth-child(2) {
  width: 68%;
}

.vb-countdown-twentytwo-bars i:nth-child(3) {
  width: 48%;
}

.vb-countdown-twentytwo-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentytwo-content h3 {
  max-width: 820px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 82px) !important;
  line-height: 0.88 !important;
  font-weight: 950 !important;
  letter-spacing: -0.085em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentytwo-content p {
  max-width: 690px;
  margin: 0 0 22px !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twentytwo-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
}

.vb-countdown-twentytwo-badges span {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.12);
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-twentytwo-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 12px);
  max-width: 720px;
  margin-bottom: 18px;
}

.vb-countdown-twentytwo-timer div {
  min-width: 0;
  padding: clamp(15px, 2.5vw, 22px) 8px;
  border-radius: 999px 999px 22px 22px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.14);
  text-align: center;
}

.vb-countdown-twentytwo-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 4.6vw, 54px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twentytwo-timer span {
  display: block;
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-twentytwo-status {
  margin-bottom: 18px !important;
}

.vb-countdown-twentytwo-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.vb-countdown-twentytwo-buttons a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 14px 20px;
  border-radius: 18px;
  background: #ffffff;
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(15, 23, 42, 0.18);
}

.vb-countdown-twentytwo-buttons a:nth-child(2) {
  background: rgba(255,255,255,0.12);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  border: 1px solid rgba(255,255,255,0.16);
}

@media (max-width: 920px) {
  .vb-countdown-twentytwo-app {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentytwo-phone {
    max-width: 260px;
  }
}

@media (max-width: 640px) {
  .vb-countdown-twentytwo-demo {
    border-radius: 38px 38px 12px 12px;
  }

  .vb-countdown-twentytwo-app {
    border-radius: 30px 30px 10px 10px;
  }

  .vb-countdown-twentytwo-content h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twentytwo-phone {
    transform: none;
  }

  .vb-countdown-twentytwo-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-twentytwo-timer strong {
    font-size: clamp(30px, 13vw, 46px);
  }

  .vb-countdown-twentytwo-buttons a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-twentytwo-timer {
    grid-template-columns: 1fr;
  }
}

This app download launch countdown timer is useful for mobile app landing pages, app store waitlists, startup product launches, beta access campaigns, SaaS app releases, and download promotion sections.

23. Preorder Countdown Timer

A preorder countdown timer is useful for product launch pages, ecommerce preorder campaigns, limited production runs, hardware launches, digital product releases, book launches, and early buyer landing pages. It shows visitors how much time remains before preorder pricing or availability changes.

This example uses a premium preorder layout with a product showcase panel, launch badge, price card, preorder benefits, and a large countdown display. The JavaScript uses a repeating 21-day preorder cycle, so the timer keeps working in the future and never stays stopped at zero.

Example 23
Preorder price €149
Preorder Campaign

Preorder Countdown Timer

Use this countdown timer for product preorder pages, ecommerce launch campaigns, limited production drops, digital releases, and early buyer offers.

Early price Preorder discount active
Limited batch First release quantity
Next preorder window loading Campaign deadline
00 Days
00 Hours
00 Min
00 Sec

Preorder window is active.

Preorder Now

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentythree]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 21;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-05T13:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextPreorderEl = timer.querySelector("[data-next-preorder]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextPreorderEnd(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextEnd = getNextPreorderEnd(now);
    let remaining = nextEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextEnd).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextPreorderEl.textContent = "Ends " + dateText;
    statusEl.textContent = "Preorder window is active.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentythree-demo">
  <section class="vb-countdown-twentythree-preorder" data-vb-countdown-twentythree data-cycle-days="21">
    <div class="vb-countdown-twentythree-product">
      <span class="vb-countdown-twentythree-tag">Example 23</span>

      <div class="vb-countdown-twentythree-product-card">
        <div class="vb-countdown-twentythree-product-top">
          <i></i>
          <i></i>
        </div>

        <div class="vb-countdown-twentythree-product-shape">
          <span></span>
        </div>

        <div class="vb-countdown-twentythree-price">
          <small>Preorder price</small>
          <strong>€149</strong>
        </div>
      </div>
    </div>

    <div class="vb-countdown-twentythree-content">
      <span class="vb-countdown-twentythree-kicker">Preorder Campaign</span>
      <h3>Preorder Countdown Timer</h3>
      <p>Use this countdown timer for product preorder pages, ecommerce launch campaigns, limited production drops, digital releases, and early buyer offers.</p>

      <div class="vb-countdown-twentythree-benefits">
        <div>
          <strong>Early price</strong>
          <span>Preorder discount active</span>
        </div>

        <div>
          <strong>Limited batch</strong>
          <span>First release quantity</span>
        </div>

        <div>
          <strong data-next-preorder>Next preorder window loading</strong>
          <span>Campaign deadline</span>
        </div>
      </div>

      <div class="vb-countdown-twentythree-timer" aria-label="Preorder countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <p class="vb-countdown-twentythree-status" data-status>Preorder window is active.</p>

      <a href="#preorder-now">Preorder Now</a>
    </div>
  </section>
</div>

CSS

.vb-countdown-twentythree-demo,
.vb-countdown-twentythree-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentythree-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 18px 18px 62px 18px;
  background:
    radial-gradient(circle at 14% 18%, rgba(251, 146, 60, 0.18), transparent 34%),
    radial-gradient(circle at 88% 20%, rgba(99, 102, 241, 0.16), transparent 34%),
    linear-gradient(135deg, #fff7ed 0%, #eef2ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(251, 146, 60, 0.22);
  box-shadow: 0 28px 80px rgba(124, 45, 18, 0.09);
}

.vb-countdown-twentythree-preorder {
  position: relative;
  display: grid;
  grid-template-columns: minmax(260px, 0.78fr) minmax(0, 1.22fr);
  gap: clamp(24px, 5vw, 46px);
  align-items: center;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(24px, 5vw, 52px);
  overflow: hidden;
  border-radius: 12px 12px 48px 12px;
  background:
    radial-gradient(circle at 22% 22%, rgba(255,255,255,0.20), transparent 34%),
    linear-gradient(135deg, #431407 0%, #9a3412 48%, #312e81 100%) !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
}

.vb-countdown-twentythree-preorder::before {
  content: "";
  position: absolute;
  left: -130px;
  bottom: -140px;
  width: 360px;
  height: 360px;
  border-radius: 35% 65% 52% 48%;
  background: rgba(251, 146, 60, 0.16);
}

.vb-countdown-twentythree-preorder::after {
  content: "";
  position: absolute;
  right: -120px;
  top: -120px;
  width: 320px;
  height: 320px;
  border-radius: 68% 32% 42% 58%;
  background: rgba(129, 140, 248, 0.14);
}

.vb-countdown-twentythree-product,
.vb-countdown-twentythree-content {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-countdown-twentythree-product {
  display: grid;
  place-items: center;
}

.vb-countdown-twentythree-tag {
  position: absolute;
  left: 10px;
  top: 10px;
  z-index: 4;
  display: inline-flex;
  padding: 8px 12px;
  border-radius: 999px 999px 999px 4px;
  background: rgba(255,255,255,0.14);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentythree-product-card {
  position: relative;
  width: min(100%, 340px);
  min-height: 430px;
  border-radius: 22px 54px 22px 54px;
  background:
    radial-gradient(circle at 30% 22%, rgba(255,255,255,0.20), transparent 34%),
    linear-gradient(145deg, rgba(255,255,255,0.14), rgba(255,255,255,0.05)) !important;
  border: 1px solid rgba(255,255,255,0.16);
  box-shadow: 0 34px 90px rgba(2, 6, 23, 0.32);
  backdrop-filter: blur(14px);
  transform: rotate(3deg);
}

.vb-countdown-twentythree-product-top {
  position: absolute;
  left: 24px;
  right: 24px;
  top: 28px;
  display: flex;
  justify-content: space-between;
}

.vb-countdown-twentythree-product-top i {
  display: block;
  width: 54px;
  height: 14px;
  border-radius: 999px;
  background: rgba(255,255,255,0.18);
}

.vb-countdown-twentythree-product-top i:nth-child(2) {
  width: 34px;
}

.vb-countdown-twentythree-product-shape {
  position: absolute;
  left: 50%;
  top: 48%;
  display: grid;
  place-items: center;
  width: 190px;
  height: 230px;
  border-radius: 42px 18px 42px 18px;
  transform: translate(-50%, -50%) rotate(-9deg);
  background:
    radial-gradient(circle at 28% 20%, rgba(255,255,255,0.34), transparent 32%),
    linear-gradient(135deg, #fb923c, #6366f1) !important;
  box-shadow: 0 30px 70px rgba(2, 6, 23, 0.24);
}

.vb-countdown-twentythree-product-shape span {
  display: block;
  width: 76px;
  height: 76px;
  border-radius: 999px;
  background: rgba(255,255,255,0.28);
  border: 1px solid rgba(255,255,255,0.24);
}

.vb-countdown-twentythree-price {
  position: absolute;
  left: 24px;
  right: 24px;
  bottom: 24px;
  display: grid;
  gap: 7px;
  padding: 18px;
  border-radius: 20px 8px 20px 8px;
  background: rgba(2, 6, 23, 0.66);
  border: 1px solid rgba(255,255,255,0.12);
}

.vb-countdown-twentythree-price small {
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-countdown-twentythree-price strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 42px;
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.06em;
}

.vb-countdown-twentythree-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 6px 999px 999px 6px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.18);
  color: #fed7aa !important;
  -webkit-text-fill-color: #fed7aa !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentythree-content h3 {
  max-width: 820px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 82px) !important;
  line-height: 0.88 !important;
  font-weight: 950 !important;
  letter-spacing: -0.085em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentythree-content p {
  max-width: 690px;
  margin: 0 0 22px !important;
  color: #ffedd5 !important;
  -webkit-text-fill-color: #ffedd5 !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twentythree-benefits {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  max-width: 760px;
  margin-bottom: 18px;
}

.vb-countdown-twentythree-benefits div {
  min-width: 0;
  padding: 15px;
  border-radius: 18px 6px 18px 6px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-countdown-twentythree-benefits strong {
  display: block;
  margin-bottom: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 16px;
  line-height: 1.15;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-countdown-twentythree-benefits span {
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  font-size: 12px;
  line-height: 1.35;
  font-weight: 750;
}

.vb-countdown-twentythree-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 12px);
  max-width: 760px;
  margin-bottom: 18px;
}

.vb-countdown-twentythree-timer div {
  min-width: 0;
  padding: clamp(15px, 2.5vw, 22px) 8px;
  border-radius: 8px 26px 8px 26px;
  background: #ffffff;
  text-align: center;
  box-shadow: 0 18px 42px rgba(2, 6, 23, 0.18);
}

.vb-countdown-twentythree-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #9a3412 !important;
  -webkit-text-fill-color: #9a3412 !important;
  font-size: clamp(28px, 4.6vw, 54px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twentythree-timer span {
  display: block;
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-twentythree-status {
  margin-bottom: 18px !important;
}

.vb-countdown-twentythree-content a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 14px 20px;
  border-radius: 999px 8px 999px 8px;
  background: linear-gradient(135deg, #fb923c, #6366f1);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(251, 146, 60, 0.22);
}

@media (max-width: 920px) {
  .vb-countdown-twentythree-preorder {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentythree-product-card {
    transform: none;
  }
}

@media (max-width: 640px) {
  .vb-countdown-twentythree-demo {
    border-radius: 12px 12px 38px 12px;
  }

  .vb-countdown-twentythree-preorder {
    border-radius: 10px 10px 30px 10px;
  }

  .vb-countdown-twentythree-content h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twentythree-benefits {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentythree-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-twentythree-timer strong {
    font-size: clamp(30px, 13vw, 46px);
  }

  .vb-countdown-twentythree-content a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-twentythree-timer {
    grid-template-columns: 1fr;
  }
}

This preorder countdown timer is useful for ecommerce preorder campaigns, product launch pages, hardware releases, book launches, limited production runs, digital product releases, and early buyer landing pages.

24. Newsletter Offer Countdown Timer

A newsletter offer countdown timer is useful for email signup sections, lead magnet pages, newsletter popups, content upgrade blocks, creator landing pages, marketing funnels, and limited-time subscriber offers.

This example uses an editorial newsletter layout with a paper-style signup card, stamp badge, email input, subscriber benefit list, and compact countdown strip. The JavaScript uses a repeating 36-hour signup offer window, so the demo keeps running and never stays expired.

Example 24
Weekly Web Design Notes Newsletter Offer Countdown Timer

Use this timer for newsletter signup offers, lead magnets, content upgrades, free download campaigns, and email marketing landing pages.

Free resource included No spam Weekly tips

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentyfour]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 129600;
  let remaining = duration;

  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const hours = Math.floor(remaining / 3600);
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Signup offer is active.";

    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentyfour-demo">
  <section class="vb-countdown-twentyfour-newsletter" data-vb-countdown-twentyfour data-duration="129600">
    <div class="vb-countdown-twentyfour-paper">
      <div class="vb-countdown-twentyfour-stamp">Example 24</div>

      <div class="vb-countdown-twentyfour-paper-head">
        <span>Weekly Web Design Notes</span>
        <strong>Newsletter Offer Countdown Timer</strong>
      </div>

      <p>Use this timer for newsletter signup offers, lead magnets, content upgrades, free download campaigns, and email marketing landing pages.</p>

      <form class="vb-countdown-twentyfour-form">
        <input type="email" placeholder="Enter your email" aria-label="Email address">
        <button type="button">Subscribe</button>
      </form>

      <div class="vb-countdown-twentyfour-list">
        <span>Free resource included</span>
        <span>No spam</span>
        <span>Weekly tips</span>
      </div>
    </div>

    <aside class="vb-countdown-twentyfour-side">
      <span class="vb-countdown-twentyfour-kicker">Subscriber bonus</span>
      <h3>Newsletter Offer Countdown Timer</h3>
      <p data-status>Signup offer is active.</p>

      <div class="vb-countdown-twentyfour-timer" aria-label="Newsletter offer countdown timer">
        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <small>Offer automatically refreshes for demo use.</small>
    </aside>
  </section>
</div>

CSS

.vb-countdown-twentyfour-demo,
.vb-countdown-twentyfour-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentyfour-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 14px 48px 14px 48px;
  background:
    radial-gradient(circle at 16% 18%, rgba(15, 23, 42, 0.08), transparent 34%),
    radial-gradient(circle at 84% 22%, rgba(20, 184, 166, 0.14), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #ecfeff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.20);
  box-shadow: 0 28px 80px rgba(15, 23, 42, 0.08);
}

.vb-countdown-twentyfour-newsletter {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 360px);
  gap: clamp(22px, 4vw, 34px);
  align-items: stretch;
  max-width: 1120px;
  margin: 0 auto;
}

.vb-countdown-twentyfour-paper {
  position: relative;
  min-width: 0;
  padding: clamp(28px, 5vw, 54px);
  border-radius: 8px;
  background:
    linear-gradient(90deg, rgba(15, 23, 42, 0.045) 1px, transparent 1px),
    linear-gradient(#ffffff, #fffaf0) !important;
  background-size: 28px 28px, auto !important;
  border: 1px solid rgba(120, 113, 108, 0.24);
  box-shadow:
    0 28px 80px rgba(15, 23, 42, 0.12),
    inset 0 1px 0 rgba(255,255,255,0.8);
}

.vb-countdown-twentyfour-paper::before {
  content: "";
  position: absolute;
  left: 26px;
  top: 0;
  bottom: 0;
  width: 3px;
  background: repeating-linear-gradient(to bottom, #14b8a6 0 14px, transparent 14px 24px);
  opacity: 0.7;
}

.vb-countdown-twentyfour-stamp {
  position: absolute;
  right: clamp(18px, 3vw, 30px);
  top: clamp(18px, 3vw, 30px);
  display: grid;
  place-items: center;
  width: 96px;
  height: 96px;
  border-radius: 999px;
  border: 2px dashed rgba(20, 184, 166, 0.70);
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 11px;
  line-height: 1.2;
  font-weight: 950;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  transform: rotate(9deg);
}

.vb-countdown-twentyfour-paper-head {
  display: grid;
  gap: 12px;
  max-width: 720px;
  padding-right: 110px;
  margin-bottom: 18px;
}

.vb-countdown-twentyfour-paper-head span {
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.vb-countdown-twentyfour-paper-head strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5.8vw, 74px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyfour-paper p {
  max-width: 690px;
  margin: 0 0 24px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: clamp(15px, 1.8vw, 17px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twentyfour-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 150px;
  gap: 10px;
  max-width: 620px;
  margin-bottom: 18px;
}

.vb-countdown-twentyfour-form input {
  min-width: 0;
  min-height: 54px;
  padding: 0 16px;
  border-radius: 6px;
  border: 1px solid rgba(100, 116, 139, 0.24);
  background: #ffffff;
  outline: none;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 14px;
  font-weight: 750;
}

.vb-countdown-twentyfour-form button {
  min-height: 54px;
  border: 0;
  border-radius: 6px;
  background: #0f172a;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 14px 30px rgba(15, 23, 42, 0.18);
}

.vb-countdown-twentyfour-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.vb-countdown-twentyfour-list span {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 6px;
  background: #ecfeff;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-twentyfour-side {
  display: grid;
  align-content: center;
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 3vw, 28px);
  border-radius: 36px 8px 36px 8px;
  background:
    radial-gradient(circle at 22% 16%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #164e63 58%, #134e4a 100%) !important;
  box-shadow: 0 26px 76px rgba(15, 23, 42, 0.18);
}

.vb-countdown-twentyfour-kicker {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(20, 184, 166, 0.14);
  border: 1px solid rgba(94, 234, 212, 0.20);
  color: #99f6e4 !important;
  -webkit-text-fill-color: #99f6e4 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.vb-countdown-twentyfour-side h3 {
  margin: 0 !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 4vw, 42px) !important;
  line-height: 0.95 !important;
  font-weight: 950 !important;
  letter-spacing: -0.065em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyfour-side p {
  margin: 0 !important;
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 700;
}

.vb-countdown-twentyfour-timer {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}

.vb-countdown-twentyfour-timer div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  min-width: 0;
  padding: 15px 16px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-countdown-twentyfour-timer strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 6vw, 46px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
}

.vb-countdown-twentyfour-timer span {
  color: #a7f3d0 !important;
  -webkit-text-fill-color: #a7f3d0 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-twentyfour-side small {
  color: #d1fae5 !important;
  -webkit-text-fill-color: #d1fae5 !important;
  font-size: 12px;
  line-height: 1.45;
  font-weight: 750;
}

@media (max-width: 900px) {
  .vb-countdown-twentyfour-newsletter {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentyfour-timer {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .vb-countdown-twentyfour-timer div {
    display: grid;
    place-items: center;
    text-align: center;
  }
}

@media (max-width: 640px) {
  .vb-countdown-twentyfour-demo {
    border-radius: 12px 34px 12px 34px;
  }

  .vb-countdown-twentyfour-stamp {
    position: static;
    width: auto;
    height: auto;
    justify-self: start;
    margin-bottom: 16px;
    padding: 10px 12px;
    transform: none;
  }

  .vb-countdown-twentyfour-paper-head {
    padding-right: 0;
  }

  .vb-countdown-twentyfour-paper-head strong {
    font-size: 38px;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twentyfour-form {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentyfour-timer {
    grid-template-columns: 1fr;
  }
}

This newsletter offer countdown timer is useful for email signup sections, lead magnet pages, creator newsletters, popup offers, content upgrade blocks, subscriber campaigns, and marketing funnel landing pages.

25. Dashboard Task Deadline Countdown Timer

A dashboard task deadline countdown timer is useful for admin dashboards, project management apps, SaaS panels, task boards, client portals, productivity tools, and internal systems where users need to complete an action before a deadline.

This example uses a dashboard-style layout with task cards, priority labels, progress indicators, a deadline panel, and a compact countdown module. The JavaScript uses a repeating 5-day task deadline cycle, so the demo keeps working over time and never remains expired.

Example 25

Dashboard Task Deadline Countdown Timer

Use this countdown timer for task dashboards, SaaS panels, project boards, client portals, admin screens, and productivity apps.

High Priority Landing page review Design approval needed
In Progress Campaign assets Final files pending
Deadline loading Task deadline Deadline countdown active

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentyfive]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 5;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-09T15:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextTaskEl = timer.querySelector("[data-next-task]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextTaskDeadline(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextDeadline = getNextTaskDeadline(now);
    let remaining = nextDeadline - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextDeadline).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextTaskEl.textContent = "Due " + dateText;
    statusEl.textContent = "Deadline countdown active";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentyfive-demo">
  <section class="vb-countdown-twentyfive-dashboard" data-vb-countdown-twentyfive data-cycle-days="5">
    <div class="vb-countdown-twentyfive-sidebar">
      <span class="vb-countdown-twentyfive-logo">D</span>

      <div class="vb-countdown-twentyfive-nav">
        <i></i>
        <i></i>
        <i></i>
      </div>
    </div>

    <div class="vb-countdown-twentyfive-main">
      <div class="vb-countdown-twentyfive-title">
        <span>Example 25</span>
        <h3>Dashboard Task Deadline Countdown Timer</h3>
        <p>Use this countdown timer for task dashboards, SaaS panels, project boards, client portals, admin screens, and productivity apps.</p>
      </div>

      <div class="vb-countdown-twentyfive-tasks">
        <div>
          <span>High Priority</span>
          <strong>Landing page review</strong>
          <small>Design approval needed</small>
        </div>

        <div>
          <span>In Progress</span>
          <strong>Campaign assets</strong>
          <small>Final files pending</small>
        </div>

        <div>
          <span data-next-task>Deadline loading</span>
          <strong>Task deadline</strong>
          <small data-status>Deadline countdown active</small>
        </div>
      </div>
    </div>

    <aside class="vb-countdown-twentyfive-panel">
      <span>Deadline Timer</span>

      <div class="vb-countdown-twentyfive-timer" aria-label="Dashboard task deadline countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <div class="vb-countdown-twentyfive-progress">
        <strong>Task progress</strong>
        <div><i></i></div>
      </div>

      <a href="#dashboard-task">Open Task</a>
    </aside>
  </section>
</div>

CSS

.vb-countdown-twentyfive-demo,
.vb-countdown-twentyfive-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentyfive-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 40px);
  border-radius: 26px 8px 26px 8px;
  background:
    radial-gradient(circle at 14% 18%, rgba(99, 102, 241, 0.14), transparent 34%),
    radial-gradient(circle at 88% 24%, rgba(14, 165, 233, 0.14), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #eef2ff 52%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.18);
  box-shadow: 0 28px 80px rgba(49, 46, 129, 0.09);
}

.vb-countdown-twentyfive-dashboard {
  display: grid;
  grid-template-columns: 76px minmax(0, 1fr) minmax(270px, 350px);
  gap: clamp(16px, 3vw, 24px);
  align-items: stretch;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(14px, 2vw, 18px);
  border-radius: 22px 6px 22px 6px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-countdown-twentyfive-sidebar {
  display: grid;
  justify-items: center;
  align-content: start;
  gap: 22px;
  padding: 16px 10px;
  border-radius: 16px 4px 16px 4px;
  background: #0f172a;
}

.vb-countdown-twentyfive-logo {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 14px 4px 14px 4px;
  background: linear-gradient(135deg, #6366f1, #0ea5e9);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 20px;
  font-weight: 950;
}

.vb-countdown-twentyfive-nav {
  display: grid;
  gap: 12px;
}

.vb-countdown-twentyfive-nav i {
  display: block;
  width: 34px;
  height: 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.18);
}

.vb-countdown-twentyfive-nav i:nth-child(2) {
  background: rgba(96, 165, 250, 0.42);
}

.vb-countdown-twentyfive-main {
  min-width: 0;
  display: grid;
  align-content: center;
  gap: 20px;
  padding: clamp(20px, 3vw, 32px);
  border-radius: 18px 6px 18px 6px;
  background:
    linear-gradient(90deg, rgba(15, 23, 42, 0.035) 1px, transparent 1px),
    linear-gradient(0deg, rgba(15, 23, 42, 0.035) 1px, transparent 1px),
    #f8fafc !important;
  background-size: 28px 28px, 28px 28px, auto !important;
}

.vb-countdown-twentyfive-title span {
  display: inline-flex;
  margin-bottom: 16px;
  padding: 8px 12px;
  border-radius: 6px;
  background: #eef2ff;
  color: #4f46e5 !important;
  -webkit-text-fill-color: #4f46e5 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentyfive-title h3 {
  max-width: 780px;
  margin: 0 0 14px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(34px, 5.6vw, 72px) !important;
  line-height: 0.9 !important;
  font-weight: 950 !important;
  letter-spacing: -0.08em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyfive-title p {
  max-width: 720px;
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.72;
  font-weight: 650;
}

.vb-countdown-twentyfive-tasks {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-countdown-twentyfive-tasks div {
  min-width: 0;
  padding: 16px;
  border-radius: 18px 6px 18px 6px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.20);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-countdown-twentyfive-tasks span {
  display: inline-flex;
  margin-bottom: 8px;
  padding: 6px 9px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.vb-countdown-twentyfive-tasks div:nth-child(1) span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-countdown-twentyfive-tasks div:nth-child(2) span {
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
}

.vb-countdown-twentyfive-tasks strong {
  display: block;
  margin-bottom: 6px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px;
  line-height: 1.15;
  font-weight: 950;
  letter-spacing: -0.045em;
}

.vb-countdown-twentyfive-tasks small {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  line-height: 1.4;
  font-weight: 750;
}

.vb-countdown-twentyfive-panel {
  display: grid;
  align-content: center;
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 3vw, 26px);
  border-radius: 6px 22px 6px 22px;
  background:
    radial-gradient(circle at 24% 16%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #1e1b4b 0%, #3730a3 58%, #0f172a 100%) !important;
}

.vb-countdown-twentyfive-panel > span {
  display: inline-flex;
  justify-self: start;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.vb-countdown-twentyfive-timer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.vb-countdown-twentyfive-timer div {
  min-width: 0;
  padding: 16px 8px;
  border-radius: 16px 4px 16px 4px;
  background: rgba(255,255,255,0.11);
  border: 1px solid rgba(255,255,255,0.13);
  text-align: center;
}

.vb-countdown-twentyfive-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 6px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 6vw, 48px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyfive-timer span {
  display: block;
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-twentyfive-progress {
  display: grid;
  gap: 9px;
}

.vb-countdown-twentyfive-progress strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-countdown-twentyfive-progress div {
  overflow: hidden;
  height: 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
}

.vb-countdown-twentyfive-progress i {
  display: block;
  width: 68%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #60a5fa, #a78bfa) !important;
}

.vb-countdown-twentyfive-panel a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 999px;
  background: #ffffff;
  color: #3730a3 !important;
  -webkit-text-fill-color: #3730a3 !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(15, 23, 42, 0.20);
}

@media (max-width: 1050px) {
  .vb-countdown-twentyfive-dashboard {
    grid-template-columns: 76px minmax(0, 1fr);
  }

  .vb-countdown-twentyfive-panel {
    grid-column: 1 / -1;
  }

  .vb-countdown-twentyfive-timer {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (max-width: 760px) {
  .vb-countdown-twentyfive-dashboard {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentyfive-sidebar {
    display: none;
  }

  .vb-countdown-twentyfive-title h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twentyfive-tasks {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentyfive-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-twentyfive-panel a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-twentyfive-timer {
    grid-template-columns: 1fr;
  }
}

This dashboard task deadline countdown timer is useful for SaaS dashboards, project management apps, task boards, admin panels, client portals, productivity apps, and deadline-focused workflow sections.

26. Fitness Challenge Countdown Timer

A fitness challenge countdown timer is useful for gym websites, fitness apps, workout programs, online coaching pages, challenge landing pages, health campaigns, and membership promotions where visitors need to join before the challenge starts.

This example uses an energetic fitness campaign design with angled panels, workout stat cards, a challenge badge, bold countdown tiles, and a high-contrast CTA. The JavaScript uses a repeating 30-day fitness challenge cycle, so the demo keeps working and never remains expired.

Example 26
30 Day Challenge
HIIT Training plan
4x Weekly workouts
Fitness Challenge

Fitness Challenge Countdown Timer

Use this countdown timer for fitness challenge pages, gym promotions, workout apps, coaching launches, health campaigns, and online training programs.

00 Days
00 Hours
00 Min
00 Sec
Next challenge loading Challenge countdown active
Join Challenge

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentysix]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 30;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-10T07:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextChallengeEl = timer.querySelector("[data-next-challenge]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextChallengeStart(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function updateCountdown() {
    const now = Date.now();
    const nextStart = getNextChallengeStart(now);
    let remaining = nextStart - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextStart).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    nextChallengeEl.textContent = "Starts " + dateText;
    statusEl.textContent = "Challenge countdown active";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentysix-demo">
  <section class="vb-countdown-twentysix-fitness" data-vb-countdown-twentysix data-cycle-days="30">
    <div class="vb-countdown-twentysix-visual">
      <span class="vb-countdown-twentysix-tag">Example 26</span>

      <div class="vb-countdown-twentysix-pulse">
        <i></i>
        <strong>30</strong>
        <span>Day Challenge</span>
      </div>

      <div class="vb-countdown-twentysix-stat vb-countdown-twentysix-stat-one">
        <strong>HIIT</strong>
        <span>Training plan</span>
      </div>

      <div class="vb-countdown-twentysix-stat vb-countdown-twentysix-stat-two">
        <strong>4x</strong>
        <span>Weekly workouts</span>
      </div>
    </div>

    <div class="vb-countdown-twentysix-content">
      <span class="vb-countdown-twentysix-kicker">Fitness Challenge</span>
      <h3>Fitness Challenge Countdown Timer</h3>
      <p>Use this countdown timer for fitness challenge pages, gym promotions, workout apps, coaching launches, health campaigns, and online training programs.</p>

      <div class="vb-countdown-twentysix-timer" aria-label="Fitness challenge countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <div class="vb-countdown-twentysix-info">
        <span data-next-challenge>Next challenge loading</span>
        <strong data-status>Challenge countdown active</strong>
      </div>

      <a href="#join-fitness-challenge">Join Challenge</a>
    </div>
  </section>
</div>

CSS

.vb-countdown-twentysix-demo,
.vb-countdown-twentysix-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentysix-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 8px 70px 8px 70px;
  background:
    radial-gradient(circle at 16% 18%, rgba(239, 68, 68, 0.16), transparent 34%),
    radial-gradient(circle at 88% 22%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(239, 68, 68, 0.18);
  box-shadow: 0 28px 80px rgba(127, 29, 29, 0.09);
}

.vb-countdown-twentysix-fitness {
  position: relative;
  display: grid;
  grid-template-columns: minmax(260px, 0.8fr) minmax(0, 1.2fr);
  gap: clamp(24px, 5vw, 46px);
  align-items: center;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(24px, 5vw, 52px);
  overflow: hidden;
  border-radius: 6px 54px 6px 54px;
  background:
    repeating-linear-gradient(-12deg, rgba(255,255,255,0.035) 0 10px, transparent 10px 22px),
    linear-gradient(135deg, #111827 0%, #7f1d1d 48%, #14532d 100%) !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.30);
}

.vb-countdown-twentysix-fitness::before {
  content: "";
  position: absolute;
  left: -120px;
  top: -120px;
  width: 340px;
  height: 340px;
  border-radius: 999px;
  background: rgba(239, 68, 68, 0.15);
}

.vb-countdown-twentysix-fitness::after {
  content: "";
  position: absolute;
  right: -140px;
  bottom: -140px;
  width: 380px;
  height: 380px;
  border-radius: 999px;
  background: rgba(34, 197, 94, 0.14);
}

.vb-countdown-twentysix-visual,
.vb-countdown-twentysix-content {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-countdown-twentysix-visual {
  min-height: clamp(340px, 43vw, 500px);
  border-radius: 6px 44px 6px 44px;
  background:
    radial-gradient(circle at 30% 20%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, rgba(255,255,255,0.12), rgba(255,255,255,0.04)) !important;
  border: 1px solid rgba(255,255,255,0.14);
  box-shadow: 0 30px 80px rgba(2, 6, 23, 0.24);
  overflow: hidden;
}

.vb-countdown-twentysix-tag {
  position: absolute;
  left: 18px;
  top: 18px;
  z-index: 3;
  display: inline-flex;
  padding: 8px 12px;
  border-radius: 999px 999px 999px 4px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #fecaca !important;
  -webkit-text-fill-color: #fecaca !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentysix-pulse {
  position: absolute;
  left: 50%;
  top: 46%;
  display: grid;
  place-items: center;
  width: clamp(190px, 28vw, 270px);
  height: clamp(190px, 28vw, 270px);
  border-radius: 999px;
  transform: translate(-50%, -50%);
  background:
    radial-gradient(circle, rgba(255,255,255,0.20), rgba(255,255,255,0.06)) !important;
  border: 1px solid rgba(255,255,255,0.16);
}

.vb-countdown-twentysix-pulse i {
  position: absolute;
  inset: 18px;
  border-radius: inherit;
  border: 2px dashed rgba(255,255,255,0.22);
}

.vb-countdown-twentysix-pulse strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(58px, 10vw, 100px);
  line-height: 0.85;
  font-weight: 950;
  letter-spacing: -0.08em;
}

.vb-countdown-twentysix-pulse span {
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 13px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-twentysix-stat {
  position: absolute;
  display: grid;
  gap: 5px;
  padding: 16px;
  border-radius: 18px 6px 18px 6px;
  background: rgba(2, 6, 23, 0.68);
  border: 1px solid rgba(255,255,255,0.12);
  backdrop-filter: blur(12px);
}

.vb-countdown-twentysix-stat-one {
  left: 24px;
  bottom: 28px;
}

.vb-countdown-twentysix-stat-two {
  right: 24px;
  top: 82px;
}

.vb-countdown-twentysix-stat strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 30px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.055em;
}

.vb-countdown-twentysix-stat span {
  color: #fecaca !important;
  -webkit-text-fill-color: #fecaca !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-twentysix-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 6px 999px 999px 6px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentysix-content h3 {
  max-width: 820px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 82px) !important;
  line-height: 0.88 !important;
  font-weight: 950 !important;
  letter-spacing: -0.085em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentysix-content p {
  max-width: 690px;
  margin: 0 0 22px !important;
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twentysix-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 12px);
  max-width: 760px;
  margin-bottom: 18px;
}

.vb-countdown-twentysix-timer div {
  min-width: 0;
  padding: clamp(15px, 2.5vw, 22px) 8px;
  border-radius: 6px 28px 6px 28px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.14);
  text-align: center;
}

.vb-countdown-twentysix-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 4.6vw, 54px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twentysix-timer span {
  display: block;
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-twentysix-info {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
}

.vb-countdown-twentysix-info span,
.vb-countdown-twentysix-info strong {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.12);
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-twentysix-content a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 14px 20px;
  border-radius: 999px 8px 999px 8px;
  background: linear-gradient(135deg, #ef4444, #22c55e);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(239, 68, 68, 0.22);
}

@media (max-width: 920px) {
  .vb-countdown-twentysix-fitness {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-countdown-twentysix-demo {
    border-radius: 8px 38px 8px 38px;
  }

  .vb-countdown-twentysix-fitness {
    border-radius: 6px 30px 6px 30px;
  }

  .vb-countdown-twentysix-content h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twentysix-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-twentysix-timer strong {
    font-size: clamp(30px, 13vw, 46px);
  }

  .vb-countdown-twentysix-stat {
    position: static;
    margin: 12px;
  }

  .vb-countdown-twentysix-pulse {
    top: 42%;
  }

  .vb-countdown-twentysix-content a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-twentysix-timer {
    grid-template-columns: 1fr;
  }
}

This fitness challenge countdown timer is useful for gym landing pages, fitness app campaigns, online coaching programs, workout challenges, health promotions, personal trainer pages, and membership offer sections.

27. Multi-Timezone Event Countdown Timer

A multi-timezone event countdown timer is useful for webinars, online conferences, product launches, livestreams, virtual events, global workshops, and international campaigns where visitors may join from different locations.

This example uses a global event layout with timezone cards, a world-style grid background, event details, and a countdown that keeps cycling forward. The JavaScript uses a repeating 14-day global event cycle and also displays example event times for New York, London, and Tokyo.

Example 27
GLOBAL
New York Loading
London Loading
Tokyo Loading
Global Event

Multi-Timezone Event Countdown Timer

Use this countdown timer for international webinars, online conferences, global launches, livestream events, workshops, and worldwide campaign pages.

00 Days
00 Hours
00 Min
00 Sec
Next event loading Global event countdown active
Register for Event

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentyseven]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 14;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-12T16:00:00Z").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextEventEl = timer.querySelector("[data-next-event]");
  const statusEl = timer.querySelector("[data-status]");
  const nyTimeEl = timer.querySelector("[data-ny-time]");
  const londonTimeEl = timer.querySelector("[data-london-time]");
  const tokyoTimeEl = timer.querySelector("[data-tokyo-time]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getNextEvent(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return baseDate + (cyclesPassed + 1) * cycleMs;
  }

  function formatTime(timestamp, timeZone) {
    return new Date(timestamp).toLocaleTimeString("en-US", {
      timeZone: timeZone,
      hour: "2-digit",
      minute: "2-digit"
    });
  }

  function updateCountdown() {
    const now = Date.now();
    const nextEvent = getNextEvent(now);
    let remaining = nextEvent - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(nextEvent).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);

    nyTimeEl.textContent = formatTime(nextEvent, "America/New_York");
    londonTimeEl.textContent = formatTime(nextEvent, "Europe/London");
    tokyoTimeEl.textContent = formatTime(nextEvent, "Asia/Tokyo");

    nextEventEl.textContent = "Next event: " + dateText;
    statusEl.textContent = "Global event countdown active";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentyseven-demo">
  <section class="vb-countdown-twentyseven-event" data-vb-countdown-twentyseven data-cycle-days="14">
    <div class="vb-countdown-twentyseven-map">
      <span class="vb-countdown-twentyseven-tag">Example 27</span>

      <div class="vb-countdown-twentyseven-orbit">
        <i></i>
        <i></i>
        <i></i>
        <strong>GLOBAL</strong>
      </div>

      <div class="vb-countdown-twentyseven-zone vb-countdown-twentyseven-zone-one">
        <span>New York</span>
        <strong data-ny-time>Loading</strong>
      </div>

      <div class="vb-countdown-twentyseven-zone vb-countdown-twentyseven-zone-two">
        <span>London</span>
        <strong data-london-time>Loading</strong>
      </div>

      <div class="vb-countdown-twentyseven-zone vb-countdown-twentyseven-zone-three">
        <span>Tokyo</span>
        <strong data-tokyo-time>Loading</strong>
      </div>
    </div>

    <div class="vb-countdown-twentyseven-content">
      <span class="vb-countdown-twentyseven-kicker">Global Event</span>
      <h3>Multi-Timezone Event Countdown Timer</h3>
      <p>Use this countdown timer for international webinars, online conferences, global launches, livestream events, workshops, and worldwide campaign pages.</p>

      <div class="vb-countdown-twentyseven-timer" aria-label="Multi-timezone event countdown timer">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <div class="vb-countdown-twentyseven-meta">
        <span data-next-event>Next event loading</span>
        <strong data-status>Global event countdown active</strong>
      </div>

      <a href="#global-event-registration">Register for Event</a>
    </div>
  </section>
</div>

CSS

.vb-countdown-twentyseven-demo,
.vb-countdown-twentyseven-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentyseven-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 40px 8px 40px 8px;
  background:
    radial-gradient(circle at 16% 18%, rgba(14, 165, 233, 0.17), transparent 34%),
    radial-gradient(circle at 88% 22%, rgba(168, 85, 247, 0.15), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #faf5ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.20);
  box-shadow: 0 28px 80px rgba(3, 105, 161, 0.09);
}

.vb-countdown-twentyseven-event {
  position: relative;
  display: grid;
  grid-template-columns: minmax(260px, 0.88fr) minmax(0, 1.12fr);
  gap: clamp(24px, 5vw, 46px);
  align-items: center;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(24px, 5vw, 52px);
  overflow: hidden;
  border-radius: 32px 6px 32px 6px;
  background:
    linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.05) 1px, transparent 1px),
    radial-gradient(circle at 20% 20%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #0f172a 0%, #075985 48%, #581c87 100%) !important;
  background-size: 34px 34px, 34px 34px, auto, auto !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.30);
}

.vb-countdown-twentyseven-event::before {
  content: "";
  position: absolute;
  right: -140px;
  top: -130px;
  width: 390px;
  height: 390px;
  border-radius: 999px;
  background: rgba(125, 211, 252, 0.13);
}

.vb-countdown-twentyseven-event::after {
  content: "";
  position: absolute;
  left: -140px;
  bottom: -160px;
  width: 390px;
  height: 390px;
  border-radius: 999px;
  background: rgba(192, 132, 252, 0.13);
}

.vb-countdown-twentyseven-map,
.vb-countdown-twentyseven-content {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-countdown-twentyseven-map {
  min-height: clamp(360px, 44vw, 520px);
  border-radius: 28px 6px 28px 6px;
  background:
    radial-gradient(circle at 50% 50%, rgba(255,255,255,0.13), transparent 34%),
    linear-gradient(135deg, rgba(255,255,255,0.12), rgba(255,255,255,0.04)) !important;
  border: 1px solid rgba(255,255,255,0.14);
  overflow: hidden;
  box-shadow: 0 30px 80px rgba(2, 6, 23, 0.24);
}

.vb-countdown-twentyseven-tag {
  position: absolute;
  left: 18px;
  top: 18px;
  z-index: 4;
  display: inline-flex;
  padding: 8px 12px;
  border-radius: 6px 999px 999px 6px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentyseven-orbit {
  position: absolute;
  left: 50%;
  top: 48%;
  display: grid;
  place-items: center;
  width: clamp(190px, 30vw, 300px);
  height: clamp(190px, 30vw, 300px);
  border-radius: 999px;
  transform: translate(-50%, -50%);
  background:
    radial-gradient(circle, rgba(255,255,255,0.17), rgba(255,255,255,0.045)) !important;
  border: 1px solid rgba(255,255,255,0.14);
}

.vb-countdown-twentyseven-orbit i {
  position: absolute;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.18);
}

.vb-countdown-twentyseven-orbit i:nth-child(1) {
  inset: 18px;
}

.vb-countdown-twentyseven-orbit i:nth-child(2) {
  inset: 42px;
}

.vb-countdown-twentyseven-orbit i:nth-child(3) {
  inset: 70px;
}

.vb-countdown-twentyseven-orbit strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(24px, 4vw, 42px);
  line-height: 1;
  font-weight: 950;
  letter-spacing: 0.12em;
}

.vb-countdown-twentyseven-zone {
  position: absolute;
  display: grid;
  gap: 5px;
  min-width: 138px;
  padding: 14px;
  border-radius: 18px 6px 18px 6px;
  background: rgba(2, 6, 23, 0.68);
  border: 1px solid rgba(255,255,255,0.12);
  backdrop-filter: blur(12px);
}

.vb-countdown-twentyseven-zone-one {
  left: 22px;
  bottom: 30px;
}

.vb-countdown-twentyseven-zone-two {
  right: 22px;
  top: 82px;
}

.vb-countdown-twentyseven-zone-three {
  right: 36px;
  bottom: 34px;
}

.vb-countdown-twentyseven-zone span {
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-twentyseven-zone strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.03em;
}

.vb-countdown-twentyseven-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px 6px 999px 6px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #e9d5ff !important;
  -webkit-text-fill-color: #e9d5ff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentyseven-content h3 {
  max-width: 820px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 82px) !important;
  line-height: 0.88 !important;
  font-weight: 950 !important;
  letter-spacing: -0.085em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyseven-content p {
  max-width: 700px;
  margin: 0 0 22px !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twentyseven-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 12px);
  max-width: 760px;
  margin-bottom: 18px;
}

.vb-countdown-twentyseven-timer div {
  min-width: 0;
  padding: clamp(15px, 2.5vw, 22px) 8px;
  border-radius: 999px 999px 8px 8px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.14);
  text-align: center;
}

.vb-countdown-twentyseven-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(28px, 4.6vw, 54px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyseven-timer span {
  display: block;
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-twentyseven-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 18px;
}

.vb-countdown-twentyseven-meta span,
.vb-countdown-twentyseven-meta strong {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.12);
  color: #e9d5ff !important;
  -webkit-text-fill-color: #e9d5ff !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-twentyseven-content a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
  padding: 14px 20px;
  border-radius: 8px 999px 8px 999px;
  background: linear-gradient(135deg, #0ea5e9, #a855f7);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(14, 165, 233, 0.22);
}

@media (max-width: 920px) {
  .vb-countdown-twentyseven-event {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-countdown-twentyseven-demo {
    border-radius: 28px 6px 28px 6px;
  }

  .vb-countdown-twentyseven-event {
    border-radius: 24px 6px 24px 6px;
  }

  .vb-countdown-twentyseven-content h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twentyseven-zone {
    position: static;
    margin: 10px;
  }

  .vb-countdown-twentyseven-orbit {
    top: 36%;
  }

  .vb-countdown-twentyseven-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-twentyseven-timer strong {
    font-size: clamp(30px, 13vw, 46px);
  }

  .vb-countdown-twentyseven-content a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-twentyseven-timer {
    grid-template-columns: 1fr;
  }
}

This multi-timezone event countdown timer is useful for global webinars, virtual conferences, livestream pages, online workshops, international product launches, and event registration landing pages.

28. Countdown Timer with Progress Bar

A countdown timer with progress bar is useful for sale campaigns, checkout timers, onboarding steps, course enrollment windows, event deadlines, dashboard alerts, product launches, and landing page offers where visitors should see both remaining time and campaign progress.

This example uses a progress-focused layout with a large horizontal progress bar, percentage indicator, timer chips, status text, and CTA button. The JavaScript uses a repeating 7-day cycle and updates both the countdown numbers and the visual progress bar.

Example 28

Countdown Timer with Progress Bar

Use this countdown timer with a progress bar for campaigns, checkout holds, sales, onboarding flows, course deadlines, dashboard alerts, and launch pages.

0% Time used
Campaign started Deadline loading
00 Days
00 Hours
00 Min
00 Sec

Progress countdown is active.

Continue Before Deadline

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentyeight]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 7;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-07T09:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const percentEl = timer.querySelector("[data-percent]");
  const progressBarEl = timer.querySelector("[data-progress-bar]");
  const nextCycleEl = timer.querySelector("[data-next-cycle]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getCycleData(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    const currentStart = baseDate + cyclesPassed * cycleMs;
    const nextEnd = baseDate + (cyclesPassed + 1) * cycleMs;

    return {
      currentStart: currentStart,
      nextEnd: nextEnd
    };
  }

  function updateCountdown() {
    const now = Date.now();
    const cycleData = getCycleData(now);
    let remaining = cycleData.nextEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const elapsed = cycleMs - remaining;
    const progress = Math.min(100, Math.max(0, Math.round((elapsed / cycleMs) * 100)));

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(cycleData.nextEnd).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);

    percentEl.textContent = progress + "%";
    progressBarEl.style.width = progress + "%";
    nextCycleEl.textContent = "Ends " + dateText;
    statusEl.textContent = "Progress countdown is active.";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentyeight-demo">
  <section class="vb-countdown-twentyeight-progress" data-vb-countdown-twentyeight data-cycle-days="7">
    <div class="vb-countdown-twentyeight-top">
      <div>
        <span class="vb-countdown-twentyeight-kicker">Example 28</span>
        <h3>Countdown Timer with Progress Bar</h3>
        <p>Use this countdown timer with a progress bar for campaigns, checkout holds, sales, onboarding flows, course deadlines, dashboard alerts, and launch pages.</p>
      </div>

      <div class="vb-countdown-twentyeight-percent">
        <strong data-percent>0%</strong>
        <span>Time used</span>
      </div>
    </div>

    <div class="vb-countdown-twentyeight-bar-wrap">
      <div class="vb-countdown-twentyeight-bar">
        <i data-progress-bar></i>
      </div>
      <div class="vb-countdown-twentyeight-bar-labels">
        <span>Campaign started</span>
        <strong data-next-cycle>Deadline loading</strong>
      </div>
    </div>

    <div class="vb-countdown-twentyeight-bottom">
      <div class="vb-countdown-twentyeight-timer" aria-label="Countdown timer with progress bar">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <div class="vb-countdown-twentyeight-action">
        <p data-status>Progress countdown is active.</p>
        <a href="#progress-countdown-offer">Continue Before Deadline</a>
      </div>
    </div>
  </section>
</div>

CSS

.vb-countdown-twentyeight-demo,
.vb-countdown-twentyeight-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentyeight-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 22px 22px 6px 60px;
  background:
    radial-gradient(circle at 14% 18%, rgba(34, 197, 94, 0.15), transparent 34%),
    radial-gradient(circle at 88% 22%, rgba(59, 130, 246, 0.14), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.20);
  box-shadow: 0 28px 80px rgba(21, 128, 61, 0.09);
}

.vb-countdown-twentyeight-progress {
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(24px, 5vw, 52px);
  overflow: hidden;
  border-radius: 18px 18px 6px 46px;
  background:
    radial-gradient(circle at 24% 18%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #052e16 0%, #14532d 52%, #1e3a8a 100%) !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
}

.vb-countdown-twentyeight-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(160px, 220px);
  gap: clamp(18px, 3vw, 28px);
  align-items: end;
  margin-bottom: clamp(22px, 4vw, 34px);
}

.vb-countdown-twentyeight-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px 999px 999px 6px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentyeight-top h3 {
  max-width: 860px;
  margin: 0 0 16px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(36px, 6vw, 84px) !important;
  line-height: 0.88 !important;
  font-weight: 950 !important;
  letter-spacing: -0.085em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyeight-top p {
  max-width: 760px;
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: clamp(15px, 1.8vw, 16px);
  line-height: 1.75;
  font-weight: 650;
}

.vb-countdown-twentyeight-percent {
  min-width: 0;
  padding: 22px;
  border-radius: 28px 8px 28px 8px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.15);
  text-align: center;
}

.vb-countdown-twentyeight-percent strong {
  display: block;
  margin-bottom: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(42px, 7vw, 70px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.08em;
  font-variant-numeric: tabular-nums;
}

.vb-countdown-twentyeight-percent span {
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-twentyeight-bar-wrap {
  margin-bottom: clamp(22px, 4vw, 34px);
  padding: clamp(16px, 3vw, 22px);
  border-radius: 26px 8px 26px 8px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-countdown-twentyeight-bar {
  overflow: hidden;
  height: clamp(22px, 4vw, 34px);
  border-radius: 999px;
  background: rgba(255,255,255,0.13);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.12);
}

.vb-countdown-twentyeight-bar i {
  display: block;
  width: 0%;
  height: 100%;
  border-radius: inherit;
  background:
    linear-gradient(90deg, #22c55e, #0ea5e9, #a855f7) !important;
  box-shadow: 0 0 26px rgba(34, 197, 94, 0.35);
  transition: width 0.4s ease;
}

.vb-countdown-twentyeight-bar-labels {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 12px;
}

.vb-countdown-twentyeight-bar-labels span,
.vb-countdown-twentyeight-bar-labels strong {
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  line-height: 1.35;
  font-weight: 850;
}

.vb-countdown-twentyeight-bottom {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(250px, 340px);
  gap: clamp(18px, 3vw, 26px);
  align-items: stretch;
}

.vb-countdown-twentyeight-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.4vw, 12px);
}

.vb-countdown-twentyeight-timer div {
  min-width: 0;
  padding: clamp(16px, 2.6vw, 24px) 8px;
  border-radius: 8px 28px 8px 28px;
  background: #ffffff;
  text-align: center;
  box-shadow: 0 18px 42px rgba(2, 6, 23, 0.18);
}

.vb-countdown-twentyeight-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
  font-size: clamp(28px, 4.6vw, 54px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-twentyeight-timer span {
  display: block;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-countdown-twentyeight-action {
  display: grid;
  align-content: center;
  gap: 14px;
  min-width: 0;
  padding: 22px;
  border-radius: 28px 8px 28px 8px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-countdown-twentyeight-action p {
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 750;
}

.vb-countdown-twentyeight-action a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 13px 18px;
  border-radius: 999px;
  background: linear-gradient(135deg, #22c55e, #0ea5e9);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(34, 197, 94, 0.22);
}

@media (max-width: 920px) {
  .vb-countdown-twentyeight-top,
  .vb-countdown-twentyeight-bottom {
    grid-template-columns: 1fr;
  }

  .vb-countdown-twentyeight-percent {
    text-align: left;
  }
}

@media (max-width: 640px) {
  .vb-countdown-twentyeight-demo {
    border-radius: 18px 18px 6px 38px;
  }

  .vb-countdown-twentyeight-progress {
    border-radius: 14px 14px 6px 30px;
  }

  .vb-countdown-twentyeight-top h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-twentyeight-bar-labels {
    flex-direction: column;
  }

  .vb-countdown-twentyeight-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-twentyeight-timer strong {
    font-size: clamp(30px, 13vw, 46px);
  }

  .vb-countdown-twentyeight-action a {
    width: 100%;
  }
}

@media (max-width: 360px) {
  .vb-countdown-twentyeight-timer {
    grid-template-columns: 1fr;
  }
}

This countdown timer with progress bar is useful for sale campaigns, checkout reservation timers, launch pages, course enrollment windows, onboarding deadlines, dashboard alerts, and time-sensitive landing page offers.

29. Compact Mobile Countdown Timer

A compact mobile countdown timer is useful for mobile landing pages, sticky offer cards, app screens, small checkout notices, limited mobile deals, product pages, and narrow website sections where the countdown must stay readable without taking too much space.

This example uses a mobile-first vertical card with a compact header, stacked timer rows, small status badge, offer text, and thumb-friendly CTA button. The JavaScript uses a repeating 24-hour mobile offer cycle, so the demo keeps working and never stays expired.

Example 29 Mobile offer active
Mobile Deal

Compact Mobile Countdown Timer

Use this compact countdown timer for mobile offers, checkout reminders, app campaigns, narrow landing page CTAs, and product page promo cards.

Hours 00
Minutes 00
Seconds 00
Mobile-only bonus Save 15% before the timer resets
Claim Mobile Offer

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-twentynine]");
  if (!timer) return;

  const duration = Number(timer.getAttribute("data-duration")) || 86400;
  let remaining = duration;

  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const statusEl = timer.querySelector("[data-status]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function updateCountdown() {
    if (remaining <= 0) {
      remaining = duration;
    }

    const hours = Math.floor(remaining / 3600);
    const minutes = Math.floor((remaining / 60) % 60);
    const seconds = remaining % 60;

    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);
    statusEl.textContent = "Mobile offer active";

    remaining -= 1;
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-twentynine-demo">
  <section class="vb-countdown-twentynine-mobile" data-vb-countdown-twentynine data-duration="86400">
    <div class="vb-countdown-twentynine-phone-card">
      <div class="vb-countdown-twentynine-top">
        <span>Example 29</span>
        <strong data-status>Mobile offer active</strong>
      </div>

      <div class="vb-countdown-twentynine-content">
        <span class="vb-countdown-twentynine-kicker">Mobile Deal</span>
        <h3>Compact Mobile Countdown Timer</h3>
        <p>Use this compact countdown timer for mobile offers, checkout reminders, app campaigns, narrow landing page CTAs, and product page promo cards.</p>
      </div>

      <div class="vb-countdown-twentynine-timer" aria-label="Compact mobile countdown timer">
        <div>
          <span>Hours</span>
          <strong data-hours>00</strong>
        </div>

        <div>
          <span>Minutes</span>
          <strong data-minutes>00</strong>
        </div>

        <div>
          <span>Seconds</span>
          <strong data-seconds>00</strong>
        </div>
      </div>

      <div class="vb-countdown-twentynine-offer">
        <small>Mobile-only bonus</small>
        <strong>Save 15% before the timer resets</strong>
      </div>

      <a href="#mobile-countdown-offer">Claim Mobile Offer</a>
    </div>
  </section>
</div>

CSS

.vb-countdown-twentynine-demo,
.vb-countdown-twentynine-demo * {
  box-sizing: border-box;
}

.vb-countdown-twentynine-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 18% 18%, rgba(244, 63, 94, 0.14), transparent 34%),
    radial-gradient(circle at 84% 18%, rgba(99, 102, 241, 0.15), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #eef2ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(244, 63, 94, 0.18);
  box-shadow: 0 28px 80px rgba(136, 19, 55, 0.08);
}

.vb-countdown-twentynine-mobile {
  display: grid;
  place-items: center;
  max-width: 1160px;
  margin: 0 auto;
  padding: clamp(18px, 4vw, 42px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 50% 12%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #111827 0%, #4c0519 52%, #312e81 100%) !important;
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.28);
}

.vb-countdown-twentynine-phone-card {
  width: min(100%, 430px);
  min-width: 0;
  padding: clamp(18px, 5vw, 28px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 26% 14%, rgba(255,255,255,0.20), transparent 34%),
    linear-gradient(145deg, rgba(255,255,255,0.16), rgba(255,255,255,0.06)) !important;
  border: 1px solid rgba(255,255,255,0.16);
  box-shadow:
    0 34px 90px rgba(2, 6, 23, 0.32),
    inset 0 1px 0 rgba(255,255,255,0.12);
  backdrop-filter: blur(14px);
}

.vb-countdown-twentynine-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}

.vb-countdown-twentynine-top span,
.vb-countdown-twentynine-top strong {
  display: inline-flex;
  padding: 8px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.11);
  border: 1px solid rgba(255,255,255,0.14);
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  line-height: 1.2;
}

.vb-countdown-twentynine-top strong {
  color: #c7d2fe !important;
  -webkit-text-fill-color: #c7d2fe !important;
  text-align: right;
}

.vb-countdown-twentynine-content {
  min-width: 0;
  margin-bottom: 20px;
}

.vb-countdown-twentynine-kicker {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 7px 10px;
  border-radius: 12px 999px 999px 12px;
  background: rgba(244, 114, 182, 0.18);
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-twentynine-content h3 {
  margin: 0 0 12px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 8vw, 48px) !important;
  line-height: 0.92 !important;
  font-weight: 950 !important;
  letter-spacing: -0.075em;
  overflow-wrap: anywhere;
}

.vb-countdown-twentynine-content p {
  margin: 0 !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 14px;
  line-height: 1.65;
  font-weight: 650;
}

.vb-countdown-twentynine-timer {
  display: grid;
  gap: 10px;
  margin-bottom: 18px;
}

.vb-countdown-twentynine-timer div {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 20px;
  background: #ffffff;
  box-shadow: 0 16px 34px rgba(2, 6, 23, 0.18);
}

.vb-countdown-twentynine-timer span {
  color: #4c0519 !important;
  -webkit-text-fill-color: #4c0519 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.10em;
  text-transform: uppercase;
}

.vb-countdown-twentynine-timer strong {
  color: #be123c !important;
  -webkit-text-fill-color: #be123c !important;
  font-size: clamp(30px, 9vw, 48px);
  line-height: 0.9;
  font-weight: 950;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
}

.vb-countdown-twentynine-offer {
  display: grid;
  gap: 6px;
  margin-bottom: 18px;
  padding: 15px;
  border-radius: 22px 8px 22px 8px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.13);
}

.vb-countdown-twentynine-offer small {
  color: #fbcfe8 !important;
  -webkit-text-fill-color: #fbcfe8 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.vb-countdown-twentynine-offer strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 16px;
  line-height: 1.25;
  font-weight: 900;
}

.vb-countdown-twentynine-phone-card a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 52px;
  padding: 14px 18px;
  border-radius: 18px;
  background: linear-gradient(135deg, #f43f5e, #6366f1);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  font-size: 14px;
  font-weight: 950;
  box-shadow: 0 16px 34px rgba(244, 63, 94, 0.24);
}

@media (max-width: 520px) {
  .vb-countdown-twentynine-demo {
    border-radius: 24px;
  }

  .vb-countdown-twentynine-mobile {
    border-radius: 22px;
    padding: 14px;
  }

  .vb-countdown-twentynine-phone-card {
    border-radius: 28px;
  }

  .vb-countdown-twentynine-top {
    align-items: flex-start;
    flex-direction: column;
  }

  .vb-countdown-twentynine-top strong {
    text-align: left;
  }
}

@media (max-width: 340px) {
  .vb-countdown-twentynine-timer div {
    grid-template-columns: 1fr;
  }
}

This compact mobile countdown timer is useful for mobile landing pages, sticky offer cards, checkout notices, app campaigns, narrow product sections, popup offers, and small responsive CTA blocks.

30. Complete Responsive Countdown Timer Section

A complete responsive countdown timer section is useful for full landing pages, ecommerce campaigns, launch pages, SaaS offers, event promotions, product releases, lead generation pages, and website sections that need a finished hero-style countdown layout.

This final example combines a full responsive layout with a large headline, feature cards, countdown timer, progress strip, CTA buttons, trust badges, and a visual side panel. The JavaScript uses a repeating 10-day campaign cycle, so the demo remains active and works long-term.

Example 30

Complete Responsive Countdown Timer Section

Use this complete countdown section for product launches, ecommerce campaigns, SaaS landing pages, event promotions, lead generation pages, and limited-time website offers.

00 Days
00 Hours
00 Min
00 Sec
Start Campaign
Next cycle loading Countdown active No signup required
Campaign Snapshot Limited offer
10 days Campaign cycle
0% Cycle progress
Responsive Mobile ready

JavaScript

(function () {
  const timer = document.querySelector("[data-vb-countdown-thirty]");
  if (!timer) return;

  const cycleDays = Number(timer.getAttribute("data-cycle-days")) || 10;
  const cycleMs = cycleDays * 24 * 60 * 60 * 1000;
  const baseDate = new Date("2026-01-11T10:00:00").getTime();

  const daysEl = timer.querySelector("[data-days]");
  const hoursEl = timer.querySelector("[data-hours]");
  const minutesEl = timer.querySelector("[data-minutes]");
  const secondsEl = timer.querySelector("[data-seconds]");
  const nextCycleEl = timer.querySelector("[data-next-cycle]");
  const statusEl = timer.querySelector("[data-status]");
  const progressBarEl = timer.querySelector("[data-progress-bar]");
  const percentEl = timer.querySelector("[data-percent]");

  function formatNumber(value) {
    return String(value).padStart(2, "0");
  }

  function getCycleData(now) {
    const cyclesPassed = Math.floor((now - baseDate) / cycleMs);
    return {
      currentStart: baseDate + cyclesPassed * cycleMs,
      nextEnd: baseDate + (cyclesPassed + 1) * cycleMs
    };
  }

  function updateCountdown() {
    const now = Date.now();
    const cycleData = getCycleData(now);
    let remaining = cycleData.nextEnd - now;

    if (remaining <= 0) {
      remaining = cycleMs;
    }

    const elapsed = cycleMs - remaining;
    const progress = Math.min(100, Math.max(0, Math.round((elapsed / cycleMs) * 100)));

    const days = Math.floor(remaining / (1000 * 60 * 60 * 24));
    const hours = Math.floor((remaining / (1000 * 60 * 60)) % 24);
    const minutes = Math.floor((remaining / (1000 * 60)) % 60);
    const seconds = Math.floor((remaining / 1000) % 60);

    const dateText = new Date(cycleData.nextEnd).toLocaleDateString("en-US", {
      month: "short",
      day: "numeric"
    });

    daysEl.textContent = formatNumber(days);
    hoursEl.textContent = formatNumber(hours);
    minutesEl.textContent = formatNumber(minutes);
    secondsEl.textContent = formatNumber(seconds);

    nextCycleEl.textContent = "Ends " + dateText;
    statusEl.textContent = "Countdown active";
    progressBarEl.style.width = progress + "%";
    percentEl.textContent = progress + "%";
  }

  updateCountdown();
  setInterval(updateCountdown, 1000);
})();

HTML

<div class="vb-countdown-thirty-demo">
  <section class="vb-countdown-thirty-section" data-vb-countdown-thirty data-cycle-days="10">
    <div class="vb-countdown-thirty-copy">
      <span class="vb-countdown-thirty-kicker">Example 30</span>
      <h3>Complete Responsive Countdown Timer Section</h3>
      <p>Use this complete countdown section for product launches, ecommerce campaigns, SaaS landing pages, event promotions, lead generation pages, and limited-time website offers.</p>

      <div class="vb-countdown-thirty-timer" aria-label="Complete responsive countdown timer section">
        <div>
          <strong data-days>00</strong>
          <span>Days</span>
        </div>

        <div>
          <strong data-hours>00</strong>
          <span>Hours</span>
        </div>

        <div>
          <strong data-minutes>00</strong>
          <span>Min</span>
        </div>

        <div>
          <strong data-seconds>00</strong>
          <span>Sec</span>
        </div>
      </div>

      <div class="vb-countdown-thirty-actions">
        <a href="#complete-countdown-offer">Start Campaign</a>
        <button type="button">View Details</button>
      </div>

      <div class="vb-countdown-thirty-badges">
        <span data-next-cycle>Next cycle loading</span>
        <span data-status>Countdown active</span>
        <span>No signup required</span>
      </div>
    </div>

    <div class="vb-countdown-thirty-visual">
      <div class="vb-countdown-thirty-card-main">
        <span>Campaign Snapshot</span>
        <strong>Limited offer</strong>
        <div class="vb-countdown-thirty-progress">
          <i data-progress-bar></i>
        </div>
      </div>

      <div class="vb-countdown-thirty-mini vb-countdown-thirty-mini-one">
        <strong>10 days</strong>
        <span>Campaign cycle</span>
      </div>

      <div class="vb-countdown-thirty-mini vb-countdown-thirty-mini-two">
        <strong data-percent>0%</strong>
        <span>Cycle progress</span>
      </div>

      <div class="vb-countdown-thirty-mini vb-countdown-thirty-mini-three">
        <strong>Responsive</strong>
        <span>Mobile ready</span>
      </div>
    </div>
  </section>
</div>

CSS

.vb-countdown-thirty-demo,
.vb-countdown-thirty-demo * {
  box-sizing: border-box;
}

.vb-countdown-thirty-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 44px);
  border-radius: 60px 20px 60px 20px;
  background:
    radial-gradient(circle at 14% 18%, rgba(56, 189, 248, 0.16), transparent 34%),
    radial-gradient(circle at 88% 22%, rgba(217, 70, 239, 0.16), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #fdf4ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(56, 189, 248, 0.20);
  box-shadow: 0 28px 80px rgba(12, 74, 110, 0.09);
}

.vb-countdown-thirty-section {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(280px, 0.88fr);
  gap: clamp(26px, 5vw, 52px);
  align-items: center;
  max-width: 1180px;
  margin: 0 auto;
  padding: clamp(24px, 5vw, 58px);
  overflow: hidden;
  border-radius: 46px 14px 46px 14px;
  background:
    radial-gradient(circle at 20% 18%, rgba(255,255,255,0.20), transparent 34%),
    linear-gradient(135deg, #020617 0%, #0f172a 42%, #581c87 100%) !important;
  box-shadow: 0 34px 96px rgba(15, 23, 42, 0.32);
}

.vb-countdown-thirty-section::before {
  content: "";
  position: absolute;
  left: -160px;
  top: -160px;
  width: 420px;
  height: 420px;
  border-radius: 36% 64% 52% 48%;
  background: rgba(56, 189, 248, 0.14);
}

.vb-countdown-thirty-section::after {
  content: "";
  position: absolute;
  right: -170px;
  bottom: -170px;
  width: 430px;
  height: 430px;
  border-radius: 70% 30% 45% 55%;
  background: rgba(217, 70, 239, 0.14);
}

.vb-countdown-thirty-copy,
.vb-countdown-thirty-visual {
  position: relative;
  z-index: 2;
  min-width: 0;
}

.vb-countdown-thirty-kicker {
  display: inline-flex;
  margin-bottom: 18px;
  padding: 8px 12px;
  border-radius: 999px 999px 999px 6px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.16);
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.vb-countdown-thirty-copy h3 {
  max-width: 880px;
  margin: 0 0 18px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(38px, 6.4vw, 88px) !important;
  line-height: 0.86 !important;
  font-weight: 950 !important;
  letter-spacing: -0.09em;
  overflow-wrap: anywhere;
}

.vb-countdown-thirty-copy p {
  max-width: 760px;
  margin: 0 0 24px !important;
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: clamp(15px, 1.8vw, 17px);
  line-height: 1.78;
  font-weight: 650;
}

.vb-countdown-thirty-timer {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(9px, 1.5vw, 13px);
  max-width: 780px;
  margin-bottom: 22px;
}

.vb-countdown-thirty-timer div {
  min-width: 0;
  padding: clamp(16px, 2.7vw, 24px) 8px;
  border-radius: 26px 8px 26px 8px;
  background: rgba(255,255,255,0.12);
  border: 1px solid rgba(255,255,255,0.14);
  text-align: center;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.09);
}

.vb-countdown-thirty-timer strong {
  display: block;
  max-width: 100%;
  margin-bottom: 7px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 4.8vw, 58px);
  line-height: 0.92;
  font-weight: 950;
  letter-spacing: -0.06em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.vb-countdown-thirty-timer span {
  display: block;
  color: #f0abfc !important;
  -webkit-text-fill-color: #f0abfc !important;
  font-size: 10px;
  font-weight: 950;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.vb-countdown-thirty-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 18px;
}

.vb-countdown-thirty-actions a,
.vb-countdown-thirty-actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 14px 20px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
}

.vb-countdown-thirty-actions a {
  background: linear-gradient(135deg, #38bdf8, #d946ef);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  text-decoration: none !important;
  box-shadow: 0 16px 34px rgba(56, 189, 248, 0.22);
}

.vb-countdown-thirty-actions button {
  border: 1px solid rgba(255,255,255,0.18);
  background: rgba(255,255,255,0.10);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
}

.vb-countdown-thirty-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.vb-countdown-thirty-badges span {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: rgba(255,255,255,0.10);
  border: 1px solid rgba(255,255,255,0.12);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 850;
}

.vb-countdown-thirty-visual {
  min-height: clamp(360px, 46vw, 540px);
  border-radius: 16px 44px 16px 44px;
  background:
    radial-gradient(circle at 34% 20%, rgba(255,255,255,0.19), transparent 34%),
    linear-gradient(145deg, rgba(255,255,255,0.14), rgba(255,255,255,0.04)) !important;
  border: 1px solid rgba(255,255,255,0.15);
  box-shadow: 0 30px 80px rgba(2, 6, 23, 0.26);
  overflow: hidden;
}

.vb-countdown-thirty-card-main {
  position: absolute;
  left: 26px;
  right: 26px;
  top: 32px;
  display: grid;
  gap: 12px;
  padding: 22px;
  border-radius: 26px 8px 26px 8px;
  background: rgba(2, 6, 23, 0.68);
  border: 1px solid rgba(255,255,255,0.12);
  backdrop-filter: blur(12px);
}

.vb-countdown-thirty-card-main span {
  color: #bae6fd !important;
  -webkit-text-fill-color: #bae6fd !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

.vb-countdown-thirty-card-main strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(30px, 5vw, 48px);
  line-height: 0.95;
  font-weight: 950;
  letter-spacing: -0.065em;
}

.vb-countdown-thirty-progress {
  overflow: hidden;
  height: 14px;
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
}

.vb-countdown-thirty-progress i {
  display: block;
  width: 0%;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #38bdf8, #d946ef) !important;
  transition: width 0.4s ease;
}

.vb-countdown-thirty-mini {
  position: absolute;
  display: grid;
  gap: 6px;
  min-width: 150px;
  padding: 16px;
  border-radius: 22px 8px 22px 8px;
  background: rgba(255,255,255,0.13);
  border: 1px solid rgba(255,255,255,0.14);
  backdrop-filter: blur(12px);
}

.vb-countdown-thirty-mini-one {
  left: 34px;
  bottom: 38px;
}

.vb-countdown-thirty-mini-two {
  right: 28px;
  top: 210px;
}

.vb-countdown-thirty-mini-three {
  right: 38px;
  bottom: 64px;
}

.vb-countdown-thirty-mini strong {
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  line-height: 1;
  font-weight: 950;
  letter-spacing: -0.05em;
}

.vb-countdown-thirty-mini span {
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 12px;
  font-weight: 850;
}

@media (max-width: 960px) {
  .vb-countdown-thirty-section {
    grid-template-columns: 1fr;
  }

  .vb-countdown-thirty-visual {
    min-height: 440px;
  }
}

@media (max-width: 640px) {
  .vb-countdown-thirty-demo {
    border-radius: 38px 14px 38px 14px;
  }

  .vb-countdown-thirty-section {
    border-radius: 30px 10px 30px 10px;
  }

  .vb-countdown-thirty-copy h3 {
    font-size: 38px !important;
    letter-spacing: -0.06em;
  }

  .vb-countdown-thirty-timer {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-countdown-thirty-timer strong {
    font-size: clamp(30px, 13vw, 48px);
  }

  .vb-countdown-thirty-actions a,
  .vb-countdown-thirty-actions button {
    width: 100%;
  }

  .vb-countdown-thirty-visual {
    min-height: auto;
    padding: 20px;
    display: grid;
    gap: 12px;
  }

  .vb-countdown-thirty-card-main,
  .vb-countdown-thirty-mini {
    position: static;
  }
}

@media (max-width: 360px) {
  .vb-countdown-thirty-timer {
    grid-template-columns: 1fr;
  }
}

This complete responsive countdown timer section is useful for launch pages, ecommerce campaigns, SaaS offers, product releases, event promotions, lead generation pages, and full-width landing page countdown sections.

EntitySchema Pro WordPress structured data and entity SEO plugin
WordPress SEO Plugin

Make Your Website Easier for Google and AI Search to Understand

EntitySchema Pro helps website owners add clear entity-based structured data to WordPress pages, posts, products, services, authors, organizations, FAQs, and local business content.

Limited offer 29€ Regular price 89€
View EntitySchema Pro

JavaScript Countdown Timer Best Practices

JavaScript countdown timers work best when they make a real deadline easier to understand. A good countdown timer should be readable, accurate, responsive, and clearly connected to the action on the page, whether that action is joining an event, buying during a sale, reserving a checkout item, downloading an app, registering for a webinar, or waiting for a launch.

The most important rule is simple: the timer must support the page goal. Do not add a countdown only because it looks dynamic. Use it when timing matters, when visitors need a deadline, or when a campaign genuinely has a limited window.

01

Use a clear deadline

Tell users what the countdown is for: a sale end, event start, product launch, booking deadline, checkout hold, or campaign expiry.

02

Keep time units readable

Days, hours, minutes, and seconds should stay readable on desktop, tablet, and mobile screens without overflowing the timer boxes.

03

Handle the zero state

Decide what happens when the timer reaches zero: show expired text, reset the demo cycle, hide the offer, disable a button, or load the next deadline.

04

Use scoped JavaScript

Use unique wrappers and selectors so multiple countdown timers can exist on the same page without conflicting with each other.

05

Use real campaign copy

The timer should explain why the user should care. Strong labels and CTA text are just as important as the countdown numbers.

06

Test real screen sizes

Countdown numbers can become wide. Always test long day values, narrow mobile screens, tablet layouts, and large desktop sections.

Responsive Countdown Timer Design Tips

Responsive countdown timer design matters because timer numbers can easily become too wide on small screens. A four-column desktop timer may look great on a landing page, but the same layout can break on mobile if the numbers, labels, padding, or borders are too large.

For mobile screens, stack timer units into two columns or one column, reduce padding, use fluid font sizes with clamp(), and make sure every number uses minmax(0, 1fr) or similar layout rules so the timer does not overflow the container.

Desktop countdown sections

Use wider layouts, side visuals, horizontal timers, large headlines, progress bars, and strong CTA areas when there is enough space.

Tablet countdown layouts

Reduce complex columns, stack visual panels when needed, and keep timer boxes large enough to scan quickly.

Mobile countdown cards

Use two-column or stacked timer layouts, full-width buttons, shorter labels, and flexible number sizing for small screens.

Common JavaScript Countdown Timer Mistakes

Common JavaScript countdown timer mistakes usually happen when a timer is built only for the first day it is published. A hardcoded date can become expired, a layout can break on mobile, or the script can stop working when the same page contains multiple countdown components.

Most countdown timer problems are easy to avoid if you decide the timer logic first. Before styling the timer, choose whether it should use a fixed date, recurring cycle, local deadline, UTC deadline, expired message, auto-reset demo behavior, or campaign-specific action.

Using expired hardcoded dates

A countdown timer that points to an old date will show zero or negative values unless you handle the expired state or use recurring logic.

Forgetting mobile overflow

Large timer numbers can break small cards. Use flexible grids, responsive font sizes, and enough width for each timer unit.

Not explaining the deadline

Users should know whether the timer is for a sale, product launch, webinar, checkout hold, booking deadline, or event start.

Using global selectors

Generic selectors can make one timer update another timer by accident. Scope each script to a unique wrapper.

Ignoring timezone behavior

Global webinars, livestreams, and launches need careful timezone handling so visitors in different regions see the correct timing.

Creating fake urgency

A timer should be useful and honest. Fake scarcity can make an ecommerce page, service page, or landing page feel untrustworthy.

JavaScript Countdown Timer FAQ

Here are common questions about JavaScript countdown timers for websites, landing pages, ecommerce campaigns, product launches, event pages, webinar registrations, checkout timers, and responsive UI sections.

A JavaScript countdown timer is an interactive component that calculates and displays the remaining time until a specific deadline, such as a sale end, event start, product launch, webinar, booking deadline, or checkout reservation expiry.

Countdown timers work well on sale pages, product launch pages, webinar pages, event landing pages, coming soon pages, checkout pages, booking pages, app launch pages, and dashboard deadline widgets.

It depends on the use case. A real event or product launch should usually use a fixed deadline. A daily deal, weekly campaign, or demo example can use recurring logic so the timer continues to work after the first cycle ends.

Use flexible CSS grid columns, responsive font sizes, short labels, enough padding, and media queries that switch four timer units into two columns or one column on smaller screens.

Yes. These examples use vanilla JavaScript, HTML, and CSS, so they can be pasted into a Gutenberg Custom HTML block. For a live website, repeated CSS and JavaScript should usually be moved into your theme or plugin files.

You can show an expired message, disable a button, hide the offer, change the CTA, display a new deadline, or reset the timer for a recurring campaign. The right behavior depends on the purpose of the timer.

Conclusion

JavaScript countdown timers are useful for websites where timing matters. They can improve sale pages, product launch sections, webinar registrations, event landing pages, checkout flows, coming soon pages, app launch pages, donation campaigns, booking deadlines, dashboard widgets, and full responsive landing page sections.

The best countdown timer is not only a set of changing numbers. It has a clear purpose, readable labels, responsive layout, strong call-to-action, honest deadline, and JavaScript logic that handles what happens when time reaches zero. That is what makes a timer useful for real users instead of just decorative animation.

You can use the examples in this guide as starting points for ecommerce campaigns, SaaS websites, landing pages, WordPress projects, event websites, online courses, app launches, nonprofit pages, and dashboard interfaces. Customize the colors, spacing, copy, dates, reset logic, and layout to match your own website design.

Continue building better website sections and interactive UI components with these related JavaScript and CSS guides. These posts work well together when building full landing pages, product pages, dashboards, forms, filters, menus, and responsive website layouts.

The JavaScript Fetch API is one of the most important tools for building modern interactive websites, dashboards, forms, search interfaces, ecommerce tools, admin panels, and real-time web applications. It lets you request data from APIs, send form data, load JSON, submit POST requests, update records, delete items, handle errors, show loading states, cancel requests, and connect the frontend to real backend data.

In this guide, you will find 30 JavaScript Fetch API examples for real website projects, including GET requests, POST JSON requests, form submissions, PUT updates, PATCH updates, DELETE requests, search requests, debounce search, pagination, load more buttons, infinite scroll, query parameters, async/await, promises, timeout handling, request cancellation, retry logic, authorization headers, FormData uploads, and complete API dashboard patterns.

This post focuses on practical Fetch API logic, not only design. Every example uses vanilla JavaScript, visible HTML, visible CSS, and copy-paste-ready code. You will learn how to fetch JSON data, render API results, handle failed requests, prevent broken UI states, work with headers, send data to a server, and build real JavaScript API request patterns. For related JavaScript functionality, you can also explore our JavaScript form validation examples, JavaScript search filter examples, JavaScript dropdown menu examples, and JavaScript calculator examples.

View All 30 JavaScript Fetch API Examples

What Is the JavaScript Fetch API?

The JavaScript Fetch API is a built-in browser feature that allows JavaScript to make HTTP requests. With fetch(), you can request data from an API, load JSON, send form data, post new records, update existing records, delete items, and connect your frontend interface to a backend service.

A basic Fetch API request often starts with a URL and returns a Response object. From that response, you can read JSON with response.json(), check if the request succeeded with response.ok, and then use the returned data to update the page. This makes Fetch API useful for search results, product lists, user dashboards, contact forms, checkout forms, admin panels, and interactive web apps.

Fetch API is commonly used with async and await because that structure makes asynchronous requests easier to read. It can also be used with .then() and .catch(). Both styles are included in this guide so you can understand real JavaScript API request patterns.

Why Fetch API Matters

Fetch API matters because most modern websites do not only show static content. They load data, submit forms, validate information, update dashboards, search records, filter results, save preferences, refresh product data, connect to third-party services, and communicate with backend APIs without reloading the page.

Fetch API is especially important for developers who build WordPress integrations, custom plugins, SaaS dashboards, API-connected landing pages, ecommerce features, admin tools, search interfaces, and JavaScript apps. Even simple website features become more powerful when they can communicate with real API endpoints.

JavaScript Fetch API Request Types

Fetch API can be used for many different request types. A simple GET request loads data. A POST request sends new data. A PUT request usually replaces an existing record. A PATCH request updates part of a record. A DELETE request removes a record. Query parameters can be used for search, filters, pagination, sorting, and API options.

The most useful Fetch API examples are not only about calling an endpoint. A real website also needs loading states, error messages, retry buttons, validation, request cancellation, response handling, safe rendering, and clear feedback for users.

This guide includes different Fetch API techniques so the examples do not repeat the same request pattern. Some examples focus on rendering data, some focus on request methods, some focus on errors, some focus on performance, and some focus on real UI behavior such as search, pagination, load more, cancel, retry, and dashboard refresh.

What Should a Good Fetch API Example Include?

A good Fetch API example should show the full request flow: user action, request start, loading state, response handling, error handling, data rendering, and final UI feedback. This is important because real API requests can fail, return empty results, take longer than expected, or return data in a different shape than expected.

Clear request purpose

The example should show what the request is doing, such as loading posts, submitting a form, updating a user, deleting a record, or searching API data.

Loading and empty states

Users should know when data is loading, when no results are found, and when a request completed successfully.

Error handling

The code should check response.ok, catch failed requests, and show useful messages instead of leaving the UI broken.

Safe data rendering

API data should be rendered carefully with clean DOM updates, readable formatting, and fallback values for missing fields.

Before building a Fetch API feature, decide what endpoint you need, which HTTP method is correct, what data should be sent, what response you expect, what should happen while loading, and what the user should see if the request fails. This planning prevents many common API bugs.

You can combine Fetch API with many other JavaScript patterns. A form validation script can validate data before sending a POST request. A search filter can request matching results from an API. A dropdown menu can change request parameters. A calculator can submit quote data to a backend. A dashboard can refresh API data on button click or interval. For more practical JavaScript UI logic, see our JavaScript form validation examples, JavaScript search filter examples, and JavaScript dropdown menu examples.

30 JavaScript Fetch API Examples

Now let’s look at 30 JavaScript Fetch API examples for real website projects. Each example focuses on a different API request pattern, such as GET, POST, PUT, PATCH, DELETE, JSON rendering, loading states, error handling, search, debounce, pagination, load more, infinite scroll, query parameters, timeout, cancel, retry, authorization headers, FormData upload, multiple requests, sequential requests, and complete dashboard behavior.

1. Basic Fetch GET Request

A basic Fetch GET request is the first Fetch API pattern every JavaScript developer should understand. It loads data from an API endpoint, checks whether the response was successful, converts the response to JSON, and displays the result in the browser.

This example uses async, await, fetch(), response.ok, response.json(), loading text, success state, and error handling. It fetches one post from a public JSON API and renders the result inside a card.

Example 01

Basic Fetch GET Request

Click the button to fetch one JSON record from an API and display it in the result card.

Ready. Click the button to start the GET request.
No API data yet

Waiting for request

The fetched JSON result will appear here.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-01-demo");
  if (!demo) return;

  const button = demo.querySelector("[data-vb-fetch-01-button]");
  const status = demo.querySelector("[data-vb-fetch-01-status]");
  const result = demo.querySelector("[data-vb-fetch-01-result]");

  async function fetchPost() {
    button.disabled = true;
    status.className = "vb-fetch-01-status";
    status.textContent = "Loading API data...";

    result.innerHTML =
      "<span>Loading</span>" +
      "<h4>Please wait</h4>" +
      "<p>The GET request is running.</p>";

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const post = await response.json();

      status.className = "vb-fetch-01-status is-success";
      status.textContent = "Success. API data loaded.";

      result.innerHTML =
        "<span>Post ID #" + post.id + "</span>" +
        "<h4>" + post.title + "</h4>" +
        "<p>" + post.body + "</p>";
    } catch (error) {
      status.className = "vb-fetch-01-status is-error";
      status.textContent = "Request failed: " + error.message;

      result.innerHTML =
        "<span>Error</span>" +
        "<h4>Could not load API data</h4>" +
        "<p>Check the endpoint URL or network connection and try again.</p>";
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", fetchPost);
})();

HTML

<div class="vb-fetch-01-demo">
  <div class="vb-fetch-01-layout">
    <div class="vb-fetch-01-info">
      <span>Example 01</span>
      <h3>Basic Fetch GET Request</h3>
      <p>Click the button to fetch one JSON record from an API and display it in the result card.</p>
    </div>

    <div class="vb-fetch-01-app">
      <button type="button" data-vb-fetch-01-button>Fetch Post</button>

      <div class="vb-fetch-01-status" data-vb-fetch-01-status>
        Ready. Click the button to start the GET request.
      </div>

      <article class="vb-fetch-01-result" data-vb-fetch-01-result>
        <span>No API data yet</span>
        <h4>Waiting for request</h4>
        <p>The fetched JSON result will appear here.</p>
      </article>
    </div>
  </div>
</div>

CSS

.vb-fetch-01-demo,
.vb-fetch-01-demo * {
  box-sizing: border-box;
}

.vb-fetch-01-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(6, 182, 212, 0.18), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(37, 99, 235, 0.18), transparent 34%),
    linear-gradient(135deg, #ecfeff 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(6, 182, 212, 0.20);
  overflow: hidden;
}

.vb-fetch-01-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: 24px;
  max-width: 1120px;
  margin: 0 auto;
  min-width: 0;
}

.vb-fetch-01-info {
  min-width: 0;
  padding: clamp(22px, 4vw, 36px);
  border-radius: 30px;
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #083344 0%, #0891b2 52%, #1d4ed8 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
  box-shadow: 0 30px 90px rgba(8, 145, 178, 0.22);
}

.vb-fetch-01-info span {
  display: inline-flex;
  margin-bottom: 15px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #cffafe !important;
  -webkit-text-fill-color: #cffafe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-01-info h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-01-info p {
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-01-app {
  display: grid;
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-fetch-01-app button {
  min-height: 50px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #06b6d4, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.24);
}

.vb-fetch-01-app button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-01-status {
  min-width: 0;
  padding: 13px 15px;
  border-radius: 16px;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  color: #075985 !important;
  -webkit-text-fill-color: #075985 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
  overflow-wrap: anywhere;
}

.vb-fetch-01-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-01-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-01-result {
  min-width: 0;
  margin: 0;
  padding: 22px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(34, 211, 238, 0.12), transparent 34%),
    linear-gradient(135deg, #f8fafc, #ffffff) !important;
  border: 1px solid #e2e8f0;
}

.vb-fetch-01-result span {
  display: inline-flex;
  margin-bottom: 12px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-01-result h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(22px, 3vw, 30px) !important;
  line-height: 1.18 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-01-result p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-fetch-01-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-01-info h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This basic Fetch GET request example is useful for learning how to request API data, parse JSON, handle request status, show loading feedback, and render a result in the browser.

2. Fetch JSON and Render Cards

Fetching JSON and rendering cards is a common pattern for blogs, product grids, user directories, dashboards, services, portfolios, and search result pages. The Fetch API loads an array of objects, and JavaScript turns each object into a visible UI card.

This example fetches a list of posts, limits the result count, loops through the JSON array with map(), and creates a responsive card grid. It also includes a clear button, loading state, empty state, success message, and error message.

Example 02

Fetch JSON and Render Cards

Load a JSON array from an API and render the returned items as responsive cards.

Click “Load Cards” to fetch JSON data.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-02-demo");
  if (!demo) return;

  const loadButton = demo.querySelector("[data-vb-fetch-02-load]");
  const clearButton = demo.querySelector("[data-vb-fetch-02-clear]");
  const message = demo.querySelector("[data-vb-fetch-02-message]");
  const grid = demo.querySelector("[data-vb-fetch-02-grid]");

  function setMessage(text, type) {
    message.className = "vb-fetch-02-message";

    if (type === "success") {
      message.classList.add("is-success");
    }

    if (type === "error") {
      message.classList.add("is-error");
    }

    message.textContent = text;
  }

  function renderCards(posts) {
    if (!posts.length) {
      grid.innerHTML = "";
      setMessage("The API returned no results.", "error");
      return;
    }

    grid.innerHTML = posts.map(function (post) {
      return (
        '<article class="vb-fetch-02-card">' +
          '<span>Post #' + post.id + '</span>' +
          '<h4>' + post.title + '</h4>' +
          '<p>' + post.body + '</p>' +
        '</article>'
      );
    }).join("");
  }

  async function loadCards() {
    loadButton.disabled = true;
    grid.innerHTML = "";
    setMessage("Loading JSON array from API...", "");

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=6");

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const posts = await response.json();

      renderCards(posts);
      setMessage("Success. " + posts.length + " cards were rendered from API data.", "success");
    } catch (error) {
      grid.innerHTML = "";
      setMessage("Request failed: " + error.message, "error");
    } finally {
      loadButton.disabled = false;
    }
  }

  function clearCards() {
    grid.innerHTML = "";
    setMessage("Cards cleared. Click “Load Cards” to fetch JSON data again.", "");
  }

  loadButton.addEventListener("click", loadCards);
  clearButton.addEventListener("click", clearCards);
})();

HTML

<div class="vb-fetch-02-demo">
  <div class="vb-fetch-02-shell">
    <div class="vb-fetch-02-header">
      <span>Example 02</span>
      <h3>Fetch JSON and Render Cards</h3>
      <p>Load a JSON array from an API and render the returned items as responsive cards.</p>
    </div>

    <div class="vb-fetch-02-toolbar">
      <button type="button" data-vb-fetch-02-load>Load Cards</button>
      <button type="button" data-vb-fetch-02-clear>Clear Cards</button>
    </div>

    <div class="vb-fetch-02-message" data-vb-fetch-02-message>
      Click “Load Cards” to fetch JSON data.
    </div>

    <div class="vb-fetch-02-grid" data-vb-fetch-02-grid></div>
  </div>
</div>

CSS

.vb-fetch-02-demo,
.vb-fetch-02-demo * {
  box-sizing: border-box;
}

.vb-fetch-02-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(168, 85, 247, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(14, 165, 233, 0.18), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #f0f9ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.16);
  overflow: hidden;
}

.vb-fetch-02-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-02-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #581c87 0%, #7c3aed 52%, #0284c7 100%) !important;
}

.vb-fetch-02-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #f3e8ff !important;
  -webkit-text-fill-color: #f3e8ff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-02-header h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-02-header p {
  max-width: 760px;
  margin: 0 !important;
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-02-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: clamp(20px, 4vw, 28px) clamp(20px, 4vw, 34px) 0;
}

.vb-fetch-02-toolbar button {
  min-height: 48px;
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
}

.vb-fetch-02-toolbar button:first-child {
  background: linear-gradient(135deg, #7c3aed, #0284c7);
  box-shadow: 0 16px 38px rgba(124, 58, 237, 0.22);
}

.vb-fetch-02-toolbar button:last-child {
  background: #0f172a;
}

.vb-fetch-02-toolbar button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-02-message {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 13px 15px;
  border-radius: 16px;
  background: #f5f3ff;
  border: 1px solid #ddd6fe;
  color: #5b21b6 !important;
  -webkit-text-fill-color: #5b21b6 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
  overflow-wrap: anywhere;
}

.vb-fetch-02-message.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-02-message.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-02-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-02-card {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(124, 58, 237, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.24);
  box-shadow: 0 16px 42px rgba(15, 23, 42, 0.07);
}

.vb-fetch-02-card span {
  display: inline-flex;
  margin-bottom: 12px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-02-card h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.28 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-02-card p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 980px) {
  .vb-fetch-02-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .vb-fetch-02-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-02-grid {
    grid-template-columns: 1fr;
  }

  .vb-fetch-02-toolbar button {
    width: 100%;
  }
}

This Fetch JSON card rendering example is useful for blog feeds, product cards, service grids, user directories, dashboard records, search results, and API-powered content sections.

3. Fetch API with Loading State

A Fetch API loading state is important because users need feedback while an API request is running. Without a loading state, the interface can feel broken, frozen, or unclear when the request takes time to finish.

This example simulates a slower request by delaying the rendering after the API response. It shows a loading spinner, disables the button during the request, displays success feedback, and catches failed requests with a clear error state.

Example 03

Fetch API with Loading State

Show users that the API request is running before the returned data appears.

API
Waiting

No profile loaded

Click the button above to fetch a user profile.

Ready to run the request.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-03-demo");
  if (!demo) return;

  const button = demo.querySelector("[data-vb-fetch-03-load]");
  const loader = demo.querySelector("[data-vb-fetch-03-loader]");
  const profile = demo.querySelector("[data-vb-fetch-03-profile]");
  const message = demo.querySelector("[data-vb-fetch-03-message]");

  function wait(milliseconds) {
    return new Promise(function (resolve) {
      setTimeout(resolve, milliseconds);
    });
  }

  async function loadProfile() {
    button.disabled = true;
    loader.hidden = false;
    message.className = "vb-fetch-03-message";
    message.textContent = "Request started. Waiting for API response...";

    profile.innerHTML =
      '<div class="vb-fetch-03-avatar">...</div>' +
      '<div>' +
        '<span>Loading</span>' +
        '<h4>Fetching profile</h4>' +
        '<p>The profile will appear after the request finishes.</p>' +
      '</div>';

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/users/1");

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const user = await response.json();

      await wait(900);

      profile.innerHTML =
        '<div class="vb-fetch-03-avatar">' + user.name.slice(0, 2).toUpperCase() + '</div>' +
        '<div>' +
          '<span>' + user.company.name + '</span>' +
          '<h4>' + user.name + '</h4>' +
          '<p>' + user.email + ' · ' + user.address.city + '</p>' +
        '</div>';

      message.textContent = "Success. Profile loaded after visible loading state.";
    } catch (error) {
      message.className = "vb-fetch-03-message is-error";
      message.textContent = "Request failed: " + error.message;

      profile.innerHTML =
        '<div class="vb-fetch-03-avatar">!</div>' +
        '<div>' +
          '<span>Error</span>' +
          '<h4>Profile unavailable</h4>' +
          '<p>The API request failed. Try again later.</p>' +
        '</div>';
    } finally {
      loader.hidden = true;
      button.disabled = false;
    }
  }

  button.addEventListener("click", loadProfile);
})();

HTML

<div class="vb-fetch-03-demo">
  <div class="vb-fetch-03-card">
    <div class="vb-fetch-03-top">
      <span>Example 03</span>
      <h3>Fetch API with Loading State</h3>
      <p>Show users that the API request is running before the returned data appears.</p>
    </div>

    <div class="vb-fetch-03-app">
      <button type="button" data-vb-fetch-03-load>Load User Profile</button>

      <div class="vb-fetch-03-loader" data-vb-fetch-03-loader hidden>
        <div></div>
        <span>Loading profile from API...</span>
      </div>

      <div class="vb-fetch-03-profile" data-vb-fetch-03-profile>
        <div class="vb-fetch-03-avatar">API</div>
        <div>
          <span>Waiting</span>
          <h4>No profile loaded</h4>
          <p>Click the button above to fetch a user profile.</p>
        </div>
      </div>

      <div class="vb-fetch-03-message" data-vb-fetch-03-message>
        Ready to run the request.
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-03-demo,
.vb-fetch-03-demo * {
  box-sizing: border-box;
}

.vb-fetch-03-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 197, 94, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(6, 182, 212, 0.18), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #ecfeff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.18);
  overflow: hidden;
}

.vb-fetch-03-card {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-03-top {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #064e3b 0%, #059669 52%, #0891b2 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-03-top span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #d1fae5 !important;
  -webkit-text-fill-color: #d1fae5 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-03-top h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-03-top p {
  max-width: 760px;
  margin: 0 !important;
  color: #cffafe !important;
  -webkit-text-fill-color: #cffafe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-03-app {
  display: grid;
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-03-app button {
  width: fit-content;
  min-height: 50px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #10b981, #06b6d4);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(6, 182, 212, 0.24);
}

.vb-fetch-03-app button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-03-loader {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #ecfeff;
  border: 1px solid #a5f3fc;
}

.vb-fetch-03-loader[hidden] {
  display: none;
}

.vb-fetch-03-loader div {
  width: 22px;
  height: 22px;
  border: 3px solid #bae6fd;
  border-top-color: #0891b2;
  border-radius: 999px;
  animation: vbFetch03Spin 0.8s linear infinite;
}

.vb-fetch-03-loader span {
  color: #0e7490 !important;
  -webkit-text-fill-color: #0e7490 !important;
  font-size: 14px;
  line-height: 1.4;
  font-weight: 850;
}

@keyframes vbFetch03Spin {
  to {
    transform: rotate(360deg);
  }
}

.vb-fetch-03-profile {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 16px;
  align-items: center;
  min-width: 0;
  padding: 22px;
  border-radius: 24px;
  background:
    radial-gradient(circle at 12% 12%, rgba(34, 197, 94, 0.12), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid #e2e8f0;
}

.vb-fetch-03-avatar {
  display: flex;
  width: 72px;
  height: 72px;
  align-items: center;
  justify-content: center;
  border-radius: 24px;
  background: linear-gradient(135deg, #10b981, #06b6d4);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 18px;
  font-weight: 950;
  letter-spacing: -0.04em;
  box-shadow: 0 14px 34px rgba(6, 182, 212, 0.24);
}

.vb-fetch-03-profile span {
  display: inline-flex;
  margin-bottom: 7px;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-03-profile h4 {
  margin: 0 0 6px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(22px, 3vw, 32px) !important;
  line-height: 1.12 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-03-profile p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-03-message {
  min-width: 0;
  padding: 13px 15px;
  border-radius: 16px;
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 800;
  overflow-wrap: anywhere;
}

.vb-fetch-03-message.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

@media (max-width: 640px) {
  .vb-fetch-03-top h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-03-app button {
    width: 100%;
  }

  .vb-fetch-03-profile {
    grid-template-columns: 1fr;
  }
}

This Fetch API loading state example is useful for dashboards, profile cards, product loading sections, search results, API widgets, admin panels, and any interface where users need clear feedback while data is loading.

4. Fetch API Error Handling

Fetch API error handling is important because fetch() does not automatically throw an error for HTTP status codes like 404 or 500. You need to check response.ok, throw your own error, and show a useful message in the interface.

This example intentionally includes buttons for a successful request and a failed request. It demonstrates try/catch, response.ok, custom error messages, error UI, success UI, and safe fallback rendering.

Example 04

Fetch API Error Handling

Test successful and failed API requests with clear error messages and safe UI feedback.

Ready

No request yet

Choose a request type above to test Fetch API error handling.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-04-demo");
  if (!demo) return;

  const successButton = demo.querySelector("[data-vb-fetch-04-success]");
  const errorButton = demo.querySelector("[data-vb-fetch-04-error]");
  const result = demo.querySelector("[data-vb-fetch-04-result]");

  function setLoading() {
    successButton.disabled = true;
    errorButton.disabled = true;
    result.className = "vb-fetch-04-result";
    result.innerHTML =
      '<span>Loading</span>' +
      '<h4>Request running</h4>' +
      '<p>The Fetch API request is waiting for a response.</p>';
  }

  function setFinished() {
    successButton.disabled = false;
    errorButton.disabled = false;
  }

  async function runRequest(url) {
    setLoading();

    try {
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error("API returned HTTP status " + response.status);
      }

      const data = await response.json();

      result.className = "vb-fetch-04-result is-success";
      result.innerHTML =
        '<span>Success</span>' +
        '<h4>' + data.title + '</h4>' +
        '<p>The request succeeded and the API returned valid JSON data.</p>';
    } catch (error) {
      result.className = "vb-fetch-04-result is-error";
      result.innerHTML =
        '<span>Error handled</span>' +
        '<h4>Request failed safely</h4>' +
        '<p>' + error.message + '. The UI did not break because the error was caught with try/catch.</p>';
    } finally {
      setFinished();
    }
  }

  successButton.addEventListener("click", function () {
    runRequest("https://jsonplaceholder.typicode.com/posts/1");
  });

  errorButton.addEventListener("click", function () {
    runRequest("https://jsonplaceholder.typicode.com/posts/999999");
  });
})();

HTML

<div class="vb-fetch-04-demo">
  <div class="vb-fetch-04-shell">
    <div class="vb-fetch-04-header">
      <span>Example 04</span>
      <h3>Fetch API Error Handling</h3>
      <p>Test successful and failed API requests with clear error messages and safe UI feedback.</p>
    </div>

    <div class="vb-fetch-04-body">
      <div class="vb-fetch-04-actions">
        <button type="button" data-vb-fetch-04-success>Run Successful Request</button>
        <button type="button" data-vb-fetch-04-error>Run Failed Request</button>
      </div>

      <div class="vb-fetch-04-result" data-vb-fetch-04-result>
        <span>Ready</span>
        <h4>No request yet</h4>
        <p>Choose a request type above to test Fetch API error handling.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-04-demo,
.vb-fetch-04-demo * {
  box-sizing: border-box;
}

.vb-fetch-04-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(239, 68, 68, 0.15), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #fef2f2 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(239, 68, 68, 0.16);
  overflow: hidden;
}

.vb-fetch-04-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-04-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #7f1d1d 0%, #dc2626 48%, #16a34a 100%) !important;
}

.vb-fetch-04-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-04-header h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-04-header p {
  max-width: 760px;
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-04-body {
  display: grid;
  gap: 18px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-04-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  min-width: 0;
}

.vb-fetch-04-actions button {
  min-height: 50px;
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
}

.vb-fetch-04-actions button:first-child {
  background: linear-gradient(135deg, #16a34a, #06b6d4);
  box-shadow: 0 16px 38px rgba(22, 163, 74, 0.22);
}

.vb-fetch-04-actions button:last-child {
  background: linear-gradient(135deg, #dc2626, #f97316);
  box-shadow: 0 16px 38px rgba(220, 38, 38, 0.22);
}

.vb-fetch-04-actions button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-04-result {
  min-width: 0;
  padding: 24px;
  border-radius: 24px;
  background:
    radial-gradient(circle at 14% 12%, rgba(100, 116, 139, 0.10), transparent 34%),
    linear-gradient(135deg, #f8fafc, #ffffff) !important;
  border: 1px solid #e2e8f0;
}

.vb-fetch-04-result.is-success {
  background:
    radial-gradient(circle at 14% 12%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4, #ffffff) !important;
  border-color: #bbf7d0;
}

.vb-fetch-04-result.is-error {
  background:
    radial-gradient(circle at 14% 12%, rgba(239, 68, 68, 0.16), transparent 34%),
    linear-gradient(135deg, #fef2f2, #ffffff) !important;
  border-color: #fecaca;
}

.vb-fetch-04-result span {
  display: inline-flex;
  margin-bottom: 12px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #e2e8f0;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-04-result.is-success span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-04-result.is-error span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-04-result h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(24px, 4vw, 38px) !important;
  line-height: 1.1 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-04-result p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.7;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  .vb-fetch-04-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-04-actions button {
    width: 100%;
  }
}

This Fetch API error handling example is useful for dashboards, admin tools, API widgets, form submissions, search interfaces, and any JavaScript project where failed requests must not break the UI.

5. Fetch API POST JSON Request

A Fetch API POST JSON request is used when JavaScript needs to send structured data to a server. This is common for contact forms, signup forms, checkout forms, admin settings, support tickets, comments, orders, and dashboard actions.

This example sends JSON with method: "POST", Content-Type: application/json, and JSON.stringify(). It uses a demo API endpoint and displays the created response object returned by the server.

Example 05

Fetch API POST JSON Request

Send a JavaScript object to an API endpoint as JSON and show the returned response.

Waiting

No POST request sent yet

{}

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-05-demo");
  if (!demo) return;

  const titleInput = demo.querySelector("[data-vb-fetch-05-title]");
  const bodyInput = demo.querySelector("[data-vb-fetch-05-body]");
  const sendButton = demo.querySelector("[data-vb-fetch-05-send]");
  const output = demo.querySelector("[data-vb-fetch-05-output]");

  async function sendPostRequest() {
    const payload = {
      title: titleInput.value.trim(),
      body: bodyInput.value.trim(),
      userId: 1
    };

    sendButton.disabled = true;
    output.className = "vb-fetch-05-output";
    output.innerHTML =
      '<span>Sending</span>' +
      '<h4>POST request running</h4>' +
      '<pre>' + JSON.stringify(payload, null, 2) + '</pre>';

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const createdPost = await response.json();

      output.className = "vb-fetch-05-output is-success";
      output.innerHTML =
        '<span>Created</span>' +
        '<h4>JSON was sent successfully</h4>' +
        '<pre>' + JSON.stringify(createdPost, null, 2) + '</pre>';
    } catch (error) {
      output.className = "vb-fetch-05-output is-error";
      output.innerHTML =
        '<span>Error</span>' +
        '<h4>POST request failed</h4>' +
        '<pre>' + error.message + '</pre>';
    } finally {
      sendButton.disabled = false;
    }
  }

  sendButton.addEventListener("click", sendPostRequest);
})();

HTML

<div class="vb-fetch-05-demo">
  <div class="vb-fetch-05-layout">
    <div class="vb-fetch-05-panel">
      <span>Example 05</span>
      <h3>Fetch API POST JSON Request</h3>
      <p>Send a JavaScript object to an API endpoint as JSON and show the returned response.</p>
    </div>

    <div class="vb-fetch-05-formbox">
      <label>
        Title
        <input type="text" value="New API post" data-vb-fetch-05-title>
      </label>

      <label>
        Message
        <textarea data-vb-fetch-05-body>Created with a Fetch API POST JSON request.</textarea>
      </label>

      <button type="button" data-vb-fetch-05-send>Send POST Request</button>

      <div class="vb-fetch-05-output" data-vb-fetch-05-output>
        <span>Waiting</span>
        <h4>No POST request sent yet</h4>
        <pre>{}</pre>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-05-demo,
.vb-fetch-05-demo * {
  box-sizing: border-box;
}

.vb-fetch-05-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(249, 115, 22, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(168, 85, 247, 0.16), transparent 34%),
    linear-gradient(135deg, #fff7ed 0%, #faf5ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(249, 115, 22, 0.16);
  overflow: hidden;
}

.vb-fetch-05-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.88fr) minmax(0, 1.12fr);
  gap: 24px;
  max-width: 1120px;
  margin: 0 auto;
  min-width: 0;
}

.vb-fetch-05-panel {
  min-width: 0;
  padding: clamp(22px, 4vw, 36px);
  border-radius: 30px;
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #7c2d12 0%, #ea580c 52%, #7e22ce 100%) !important;
  box-shadow: 0 30px 90px rgba(249, 115, 22, 0.20);
}

.vb-fetch-05-panel span {
  display: inline-flex;
  margin-bottom: 15px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #ffedd5 !important;
  -webkit-text-fill-color: #ffedd5 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-05-panel h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-05-panel p {
  margin: 0 !important;
  color: #f3e8ff !important;
  -webkit-text-fill-color: #f3e8ff !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-05-formbox {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-fetch-05-formbox label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #9a3412 !important;
  -webkit-text-fill-color: #9a3412 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-05-formbox input,
.vb-fetch-05-formbox textarea {
  width: 100%;
  min-width: 0;
  border: 1px solid #fed7aa;
  border-radius: 16px;
  background: #fff7ed;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-fetch-05-formbox input {
  min-height: 50px;
  padding: 0 14px;
}

.vb-fetch-05-formbox textarea {
  min-height: 105px;
  padding: 14px;
  resize: vertical;
}

.vb-fetch-05-formbox button {
  min-height: 50px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #ea580c, #7e22ce);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(234, 88, 12, 0.22);
}

.vb-fetch-05-formbox button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-05-output {
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background: #fff7ed;
  border: 1px solid #fed7aa;
}

.vb-fetch-05-output.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-05-output.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-05-output span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #ffedd5;
  color: #9a3412 !important;
  -webkit-text-fill-color: #9a3412 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-05-output h4 {
  margin: 0 0 12px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 22px !important;
  line-height: 1.2 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-05-output pre {
  max-width: 100%;
  max-height: 210px;
  overflow: auto;
  margin: 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #d1fae5 !important;
  -webkit-text-fill-color: #d1fae5 !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-fetch-05-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-05-panel h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API POST JSON request example is useful for contact forms, signup forms, comment forms, order forms, settings panels, support tickets, admin dashboards, and any JavaScript feature that needs to send JSON data to an API.

6. Fetch API Form Submit Example

A Fetch API form submit example shows how to submit form data with JavaScript without reloading the page. This pattern is useful for contact forms, newsletter forms, quote request forms, booking forms, support forms, surveys, and lead generation pages.

This example prevents the default form submit, collects values with FormData, converts the data into a plain object, sends it as JSON with fetch(), and displays a success or error message after the API responds.

Example 06

Fetch API Form Submit

Submit form data as JSON with Fetch API and show the response without reloading the page.

Ready

Form has not been submitted yet

The response message will appear here after the Fetch API form submit.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-06-demo");
  if (!demo) return;

  const form = demo.querySelector("[data-vb-fetch-06-form]");
  const submitButton = demo.querySelector("[data-vb-fetch-06-submit]");
  const responseBox = demo.querySelector("[data-vb-fetch-06-response]");

  form.addEventListener("submit", async function (event) {
    event.preventDefault();

    const formData = new FormData(form);
    const payload = Object.fromEntries(formData.entries());

    submitButton.disabled = true;
    submitButton.textContent = "Submitting...";

    responseBox.className = "vb-fetch-06-response";
    responseBox.innerHTML =
      '<span>Sending</span>' +
      '<h4>Submitting form data</h4>' +
      '<p>The form values are being sent as JSON with Fetch API.</p>';

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const result = await response.json();

      responseBox.className = "vb-fetch-06-response is-success";
      responseBox.innerHTML =
        '<span>Success</span>' +
        '<h4>Form submitted without reload</h4>' +
        '<p>API returned demo ID #' + result.id + '. Submitted request type: ' + payload.requestType + '.</p>';
    } catch (error) {
      responseBox.className = "vb-fetch-06-response is-error";
      responseBox.innerHTML =
        '<span>Error</span>' +
        '<h4>Form submit failed</h4>' +
        '<p>' + error.message + '</p>';
    } finally {
      submitButton.disabled = false;
      submitButton.textContent = "Submit Form with Fetch";
    }
  });
})();

HTML

<div class="vb-fetch-06-demo">
  <div class="vb-fetch-06-shell">
    <div class="vb-fetch-06-header">
      <span>Example 06</span>
      <h3>Fetch API Form Submit</h3>
      <p>Submit form data as JSON with Fetch API and show the response without reloading the page.</p>
    </div>

    <form class="vb-fetch-06-form" data-vb-fetch-06-form>
      <div class="vb-fetch-06-grid">
        <label>
          Name
          <input type="text" name="name" value="Alex Johnson" required>
        </label>

        <label>
          Email
          <input type="email" name="email" value="alex@example.com" required>
        </label>
      </div>

      <label>
        Request type
        <select name="requestType">
          <option value="Website project">Website project</option>
          <option value="API integration">API integration</option>
          <option value="Custom dashboard">Custom dashboard</option>
        </select>
      </label>

      <label>
        Message
        <textarea name="message" required>I want to build a website with API-connected forms and dynamic content.</textarea>
      </label>

      <button type="submit" data-vb-fetch-06-submit>Submit Form with Fetch</button>
    </form>

    <div class="vb-fetch-06-response" data-vb-fetch-06-response>
      <span>Ready</span>
      <h4>Form has not been submitted yet</h4>
      <p>The response message will appear here after the Fetch API form submit.</p>
    </div>
  </div>
</div>

CSS

.vb-fetch-06-demo,
.vb-fetch-06-demo * {
  box-sizing: border-box;
}

.vb-fetch-06-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(37, 99, 235, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.16);
  overflow: hidden;
}

.vb-fetch-06-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-06-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #1e3a8a 0%, #2563eb 52%, #16a34a 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-06-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-06-header h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-06-header p {
  max-width: 760px;
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-06-form {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
  border-bottom: 1px solid rgba(148, 163, 184, 0.18);
}

.vb-fetch-06-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
}

.vb-fetch-06-form label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-06-form input,
.vb-fetch-06-form select,
.vb-fetch-06-form textarea {
  width: 100%;
  min-width: 0;
  border: 1px solid #bfdbfe;
  border-radius: 16px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-fetch-06-form input,
.vb-fetch-06-form select {
  min-height: 50px;
  padding: 0 14px;
}

.vb-fetch-06-form textarea {
  min-height: 110px;
  padding: 14px;
  resize: vertical;
}

.vb-fetch-06-form button {
  width: fit-content;
  min-height: 50px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #16a34a);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.24);
}

.vb-fetch-06-form button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-06-response {
  min-width: 0;
  margin: clamp(20px, 4vw, 34px);
  padding: 22px;
  border-radius: 24px;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
}

.vb-fetch-06-response.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-06-response.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-06-response span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-06-response h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(23px, 4vw, 34px) !important;
  line-height: 1.15 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-06-response p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.7;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 700px) {
  .vb-fetch-06-grid {
    grid-template-columns: 1fr;
  }

  .vb-fetch-06-form button {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-06-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API form submit example is useful for contact forms, lead forms, newsletter forms, booking forms, quote request forms, support forms, surveys, and custom WordPress AJAX-style form interfaces.

7. Fetch API PUT Update Request

A Fetch API PUT update request is used when JavaScript needs to replace an existing record with a full updated object. This is common in admin dashboards, profile editors, CMS tools, product editors, settings pages, and account management screens.

This example sends a complete updated post object to a demo API endpoint with method: "PUT". It collects field values, sends JSON, checks response.ok, parses the returned response, and updates the UI after the request succeeds.

Example 07

Fetch API PUT Update Request

Replace an existing API record with a complete updated JSON object using a PUT request.

Waiting

No update sent yet

The updated API response will appear here after the PUT request.

{}

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-07-demo");
  if (!demo) return;

  const titleInput = demo.querySelector("[data-vb-fetch-07-title]");
  const bodyInput = demo.querySelector("[data-vb-fetch-07-body-input]");
  const userInput = demo.querySelector("[data-vb-fetch-07-user]");
  const button = demo.querySelector("[data-vb-fetch-07-update]");
  const resultBox = demo.querySelector("[data-vb-fetch-07-result]");

  async function updateRecord() {
    const payload = {
      id: 1,
      title: titleInput.value.trim(),
      body: bodyInput.value.trim(),
      userId: Number(userInput.value) || 1
    };

    button.disabled = true;
    resultBox.className = "vb-fetch-07-result";
    resultBox.innerHTML =
      '<span>Updating</span>' +
      '<h4>PUT request running</h4>' +
      '<p>Sending the complete replacement object to the API.</p>' +
      '<pre>' + JSON.stringify(payload, null, 2) + '</pre>';

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts/1", {
        method: "PUT",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const updated = await response.json();

      resultBox.className = "vb-fetch-07-result is-success";
      resultBox.innerHTML =
        '<span>Updated</span>' +
        '<h4>Record replaced successfully</h4>' +
        '<p>The demo API returned the updated object from the PUT request.</p>' +
        '<pre>' + JSON.stringify(updated, null, 2) + '</pre>';
    } catch (error) {
      resultBox.className = "vb-fetch-07-result is-error";
      resultBox.innerHTML =
        '<span>Error</span>' +
        '<h4>PUT request failed</h4>' +
        '<p>' + error.message + '</p>' +
        '<pre>{}</pre>';
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", updateRecord);
})();

HTML

<div class="vb-fetch-07-demo">
  <div class="vb-fetch-07-shell">
    <div class="vb-fetch-07-header">
      <span>Example 07</span>
      <h3>Fetch API PUT Update Request</h3>
      <p>Replace an existing API record with a complete updated JSON object using a PUT request.</p>
    </div>

    <div class="vb-fetch-07-body">
      <div class="vb-fetch-07-form">
        <label>
          Post title
          <input type="text" value="Updated dashboard announcement" data-vb-fetch-07-title>
        </label>

        <label>
          Post body
          <textarea data-vb-fetch-07-body-input>This full record was updated with a JavaScript Fetch API PUT request.</textarea>
        </label>

        <label>
          User ID
          <input type="number" min="1" value="1" data-vb-fetch-07-user>
        </label>

        <button type="button" data-vb-fetch-07-update>Send PUT Update</button>
      </div>

      <div class="vb-fetch-07-result" data-vb-fetch-07-result>
        <span>Waiting</span>
        <h4>No update sent yet</h4>
        <p>The updated API response will appear here after the PUT request.</p>
        <pre>{}</pre>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-07-demo,
.vb-fetch-07-demo * {
  box-sizing: border-box;
}

.vb-fetch-07-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(59, 130, 246, 0.18), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(99, 102, 241, 0.18), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #eef2ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.18);
  overflow: hidden;
}

.vb-fetch-07-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-07-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #1e3a8a 0%, #2563eb 52%, #4f46e5 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-07-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-07-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-07-header p {
  max-width: 760px;
  margin: 0 !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-07-body {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
  gap: 22px;
  padding: clamp(20px, 4vw, 34px);
  min-width: 0;
}

.vb-fetch-07-form {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
}

.vb-fetch-07-form label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-07-form input,
.vb-fetch-07-form textarea {
  width: 100%;
  min-width: 0;
  border: 1px solid #bfdbfe;
  border-radius: 16px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-fetch-07-form input {
  min-height: 50px;
  padding: 0 14px;
}

.vb-fetch-07-form textarea {
  min-height: 120px;
  padding: 14px;
  resize: vertical;
}

.vb-fetch-07-form button {
  min-height: 50px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #4f46e5);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.24);
}

.vb-fetch-07-form button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-07-result {
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background:
    radial-gradient(circle at 14% 12%, rgba(37, 99, 235, 0.12), transparent 34%),
    linear-gradient(135deg, #f8fafc, #ffffff) !important;
  border: 1px solid #e2e8f0;
}

.vb-fetch-07-result.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-07-result.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-07-result span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-07-result.is-success span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-07-result.is-error span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-07-result h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(23px, 4vw, 34px) !important;
  line-height: 1.15 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-07-result p {
  margin: 0 0 14px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.7;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-07-result pre {
  max-width: 100%;
  max-height: 230px;
  overflow: auto;
  margin: 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-fetch-07-body {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-07-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This PUT update example is useful for profile editors, admin dashboards, CMS edit screens, product management tools, settings pages, and any JavaScript interface that replaces an existing API record.

8. Fetch API PATCH Partial Update

A Fetch API PATCH request updates only part of an existing record instead of replacing the entire object. This is useful for toggling status, changing a title, updating a quantity, saving a preference, marking a task complete, or changing one field in a dashboard.

This example updates only the completion status of a todo item. It sends a small JSON payload with method: "PATCH", renders the returned result, and changes the UI state based on the updated value.

Example 08

Fetch API PATCH Partial Update

Toggle a task status and send only the changed field to the API with PATCH.

Incomplete API task #1

Complete the JavaScript Fetch API lesson

{ "completed": false }

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-08-demo");
  if (!demo) return;

  const taskBox = demo.querySelector("[data-vb-fetch-08-task]");
  const badge = demo.querySelector("[data-vb-fetch-08-badge]");
  const button = demo.querySelector("[data-vb-fetch-08-toggle]");
  const output = demo.querySelector("[data-vb-fetch-08-output]");

  let completed = false;

  function renderState(data) {
    completed = Boolean(data.completed);
    taskBox.classList.toggle("is-complete", completed);
    badge.textContent = completed ? "Complete" : "Incomplete";
    button.textContent = completed ? "Mark as Incomplete" : "Mark as Complete";
    output.textContent = JSON.stringify(data, null, 2);
  }

  async function patchTask() {
    const nextStatus = !completed;
    const payload = {
      completed: nextStatus
    };

    button.disabled = true;
    output.textContent = "Sending PATCH request...\n" + JSON.stringify(payload, null, 2);

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
        method: "PATCH",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const updatedTask = await response.json();
      renderState(updatedTask);
    } catch (error) {
      output.textContent = "PATCH request failed: " + error.message;
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", patchTask);
  renderState({ id: 1, title: "Complete the JavaScript Fetch API lesson", completed: completed });
})();

HTML

<div class="vb-fetch-08-demo">
  <div class="vb-fetch-08-card">
    <div class="vb-fetch-08-copy">
      <span>Example 08</span>
      <h3>Fetch API PATCH Partial Update</h3>
      <p>Toggle a task status and send only the changed field to the API with PATCH.</p>
    </div>

    <div class="vb-fetch-08-task" data-vb-fetch-08-task>
      <div class="vb-fetch-08-task-top">
        <span data-vb-fetch-08-badge>Incomplete</span>
        <strong>API task #1</strong>
      </div>

      <p>Complete the JavaScript Fetch API lesson</p>

      <button type="button" data-vb-fetch-08-toggle>Mark as Complete</button>

      <pre data-vb-fetch-08-output>{ "completed": false }</pre>
    </div>
  </div>
</div>

CSS

.vb-fetch-08-demo,
.vb-fetch-08-demo * {
  box-sizing: border-box;
}

.vb-fetch-08-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 197, 94, 0.18), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(20, 184, 166, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #f0fdfa 54%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.18);
  overflow: hidden;
}

.vb-fetch-08-card {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  gap: 24px;
  max-width: 1120px;
  margin: 0 auto;
  min-width: 0;
}

.vb-fetch-08-copy {
  min-width: 0;
  padding: clamp(22px, 4vw, 36px);
  border-radius: 30px;
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #052e16 0%, #16a34a 54%, #0f766e 100%) !important;
  box-shadow: 0 30px 90px rgba(22, 163, 74, 0.22);
}

.vb-fetch-08-copy span {
  display: inline-flex;
  margin-bottom: 15px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-08-copy h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-08-copy p {
  margin: 0 !important;
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-08-task {
  display: grid;
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-fetch-08-task.is-complete {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-08-task-top {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  justify-content: space-between;
  min-width: 0;
}

.vb-fetch-08-task-top span {
  display: inline-flex;
  padding: 8px 11px;
  border-radius: 999px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-fetch-08-task.is-complete .vb-fetch-08-task-top span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-08-task-top strong {
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px;
  font-weight: 950;
}

.vb-fetch-08-task p {
  margin: 0 !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(24px, 4vw, 40px);
  line-height: 1.08;
  font-weight: 950;
  letter-spacing: -0.05em;
  overflow-wrap: anywhere;
}

.vb-fetch-08-task button {
  min-height: 50px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #16a34a, #0f766e);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(22, 163, 74, 0.22);
}

.vb-fetch-08-task button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-08-task pre {
  max-width: 100%;
  max-height: 220px;
  overflow: auto;
  margin: 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-fetch-08-card {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-08-copy h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This PATCH partial update example is useful for task apps, admin dashboards, status toggles, user settings, ecommerce quantity changes, notification preferences, and any API interface that updates only one field.

9. Fetch API DELETE Request

A Fetch API DELETE request is used when JavaScript needs to remove an existing resource from an API. This pattern is common in admin dashboards, user management screens, cart items, todo apps, saved lists, comments, messages, and product management tools.

This example renders a small list of demo records and lets users delete one item. The JavaScript sends a DELETE request to the API, waits for a successful response, and removes the item from the local UI.

Example 09

Fetch API DELETE Request

Send a DELETE request and remove the deleted item from the interface after success.

Choose an item to delete. The demo API will confirm the request.
API record #1 Demo dashboard item
API record #2 Demo saved message
API record #3 Demo product note

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-09-demo");
  if (!demo) return;

  const list = demo.querySelector("[data-vb-fetch-09-list]");
  const status = demo.querySelector("[data-vb-fetch-09-status]");

  async function deleteItem(item) {
    const id = item.dataset.id;
    const button = item.querySelector("button");

    button.disabled = true;
    button.textContent = "Deleting...";
    item.classList.add("is-removing");
    status.className = "vb-fetch-09-status";
    status.textContent = "Sending DELETE request for item #" + id + "...";

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts/" + id, {
        method: "DELETE"
      });

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      item.remove();
      status.className = "vb-fetch-09-status is-success";
      status.textContent = "Item #" + id + " was deleted successfully and removed from the UI.";

      if (!list.querySelector(".vb-fetch-09-item")) {
        status.textContent = "All demo items were deleted successfully.";
      }
    } catch (error) {
      item.classList.remove("is-removing");
      button.disabled = false;
      button.textContent = "Delete";
      status.className = "vb-fetch-09-status is-error";
      status.textContent = "DELETE request failed: " + error.message;
    }
  }

  list.addEventListener("click", function (event) {
    const button = event.target.closest("button");
    if (!button) return;

    const item = button.closest(".vb-fetch-09-item");
    if (!item) return;

    deleteItem(item);
  });
})();

HTML

<div class="vb-fetch-09-demo">
  <div class="vb-fetch-09-shell">
    <div class="vb-fetch-09-header">
      <span>Example 09</span>
      <h3>Fetch API DELETE Request</h3>
      <p>Send a DELETE request and remove the deleted item from the interface after success.</p>
    </div>

    <div class="vb-fetch-09-status" data-vb-fetch-09-status>
      Choose an item to delete. The demo API will confirm the request.
    </div>

    <div class="vb-fetch-09-list" data-vb-fetch-09-list>
      <div class="vb-fetch-09-item" data-id="1">
        <div>
          <strong>API record #1</strong>
          <span>Demo dashboard item</span>
        </div>
        <button type="button">Delete</button>
      </div>

      <div class="vb-fetch-09-item" data-id="2">
        <div>
          <strong>API record #2</strong>
          <span>Demo saved message</span>
        </div>
        <button type="button">Delete</button>
      </div>

      <div class="vb-fetch-09-item" data-id="3">
        <div>
          <strong>API record #3</strong>
          <span>Demo product note</span>
        </div>
        <button type="button">Delete</button>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-09-demo,
.vb-fetch-09-demo * {
  box-sizing: border-box;
}

.vb-fetch-09-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(244, 63, 94, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(15, 23, 42, 0.12), transparent 34%),
    linear-gradient(135deg, #fff1f2 0%, #f8fafc 54%, #ffffff 100%) !important;
  border: 1px solid rgba(244, 63, 94, 0.16);
  overflow: hidden;
}

.vb-fetch-09-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-09-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #7f1d1d 0%, #e11d48 52%, #0f172a 100%) !important;
}

.vb-fetch-09-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #ffe4e6 !important;
  -webkit-text-fill-color: #ffe4e6 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-09-header h3 {
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-09-header p {
  max-width: 760px;
  margin: 0 !important;
  color: #e2e8f0 !important;
  -webkit-text-fill-color: #e2e8f0 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-09-status {
  margin: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #fff1f2;
  border: 1px solid #fecdd3;
  color: #9f1239 !important;
  -webkit-text-fill-color: #9f1239 !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-09-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-09-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-09-list {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-09-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 16px;
  align-items: center;
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 12% 12%, rgba(244, 63, 94, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-09-item.is-removing {
  opacity: 0.65;
}

.vb-fetch-09-item strong {
  display: block;
  margin-bottom: 4px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px;
  line-height: 1.25;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-09-item span {
  display: block;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.vb-fetch-09-item button {
  min-height: 42px;
  padding: 0 16px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #e11d48, #7f1d1d);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
  cursor: pointer;
}

.vb-fetch-09-item button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

@media (max-width: 640px) {
  .vb-fetch-09-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-09-item {
    grid-template-columns: 1fr;
  }

  .vb-fetch-09-item button {
    width: 100%;
  }
}

This Fetch API DELETE request example is useful for admin dashboards, todo apps, user management tools, comment systems, saved lists, shopping carts, CRM notes, and any interface where JavaScript removes an item after an API request.

10. Fetch API Search Request

A Fetch API search request is useful when users type a keyword and JavaScript requests matching data from an API. This pattern is common in blog search, product search, user search, documentation search, support center search, and dashboard filters.

This example uses a search input, a search button, URLSearchParams, a Fetch API GET request, result filtering, loading feedback, empty state, and error handling. The API is a demo endpoint, but the same pattern works with real search APIs.

Example 10

Fetch API Search Request

Search API data with a keyword, build query parameters, and render matching results.

Enter a keyword and click “Search API”.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-10-demo");
  if (!demo) return;

  const input = demo.querySelector("[data-vb-fetch-10-input]");
  const button = demo.querySelector("[data-vb-fetch-10-button]");
  const status = demo.querySelector("[data-vb-fetch-10-status]");
  const results = demo.querySelector("[data-vb-fetch-10-results]");

  function setStatus(text, type) {
    status.className = "vb-fetch-10-status";

    if (type === "success") {
      status.classList.add("is-success");
    }

    if (type === "error") {
      status.classList.add("is-error");
    }

    status.textContent = text;
  }

  function renderResults(posts) {
    if (!posts.length) {
      results.innerHTML = "";
      setStatus("No matching posts found. Try another keyword.", "error");
      return;
    }

    results.innerHTML = posts.map(function (post) {
      return (
        '<article class="vb-fetch-10-result-card">' +
          '<span>Post #' + post.id + '</span>' +
          '<h4>' + post.title + '</h4>' +
          '<p>' + post.body + '</p>' +
        '</article>'
      );
    }).join("");

    setStatus("Found " + posts.length + " matching API results.", "success");
  }

  async function searchApi() {
    const keyword = input.value.trim().toLowerCase();

    if (!keyword) {
      results.innerHTML = "";
      setStatus("Please enter a search keyword.", "error");
      return;
    }

    const params = new URLSearchParams({
      _limit: "20"
    });

    button.disabled = true;
    results.innerHTML = "";
    setStatus("Searching API for “" + keyword + "”...", "");

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts?" + params.toString());

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const posts = await response.json();
      const filtered = posts.filter(function (post) {
        return post.title.toLowerCase().includes(keyword) || post.body.toLowerCase().includes(keyword);
      });

      renderResults(filtered.slice(0, 6));
    } catch (error) {
      results.innerHTML = "";
      setStatus("Search request failed: " + error.message, "error");
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", searchApi);

  input.addEventListener("keydown", function (event) {
    if (event.key === "Enter") {
      searchApi();
    }
  });
})();

HTML

<div class="vb-fetch-10-demo">
  <div class="vb-fetch-10-shell">
    <div class="vb-fetch-10-header">
      <span>Example 10</span>
      <h3>Fetch API Search Request</h3>
      <p>Search API data with a keyword, build query parameters, and render matching results.</p>
    </div>

    <div class="vb-fetch-10-search">
      <label>
        Search posts
        <input type="search" value="qui" placeholder="Try: qui, sunt, autem" data-vb-fetch-10-input>
      </label>
      <button type="button" data-vb-fetch-10-button>Search API</button>
    </div>

    <div class="vb-fetch-10-status" data-vb-fetch-10-status>
      Enter a keyword and click “Search API”.
    </div>

    <div class="vb-fetch-10-results" data-vb-fetch-10-results></div>
  </div>
</div>

CSS

.vb-fetch-10-demo,
.vb-fetch-10-demo * {
  box-sizing: border-box;
}

.vb-fetch-10-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(14, 165, 233, 0.18), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.18);
  overflow: hidden;
}

.vb-fetch-10-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-10-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #075985 0%, #0284c7 52%, #16a34a 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-10-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-10-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-10-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-10-search {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  align-items: end;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
}

.vb-fetch-10-search label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #075985 !important;
  -webkit-text-fill-color: #075985 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-10-search input {
  width: 100%;
  min-width: 0;
  min-height: 52px;
  padding: 0 16px;
  border: 1px solid #bae6fd;
  border-radius: 18px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px;
  font-weight: 800;
  outline: none;
}

.vb-fetch-10-search button {
  min-height: 52px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #0284c7, #16a34a);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(14, 165, 233, 0.22);
}

.vb-fetch-10-search button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-10-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  color: #075985 !important;
  -webkit-text-fill-color: #075985 !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-10-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-10-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-10-results {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-10-result-card {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(14, 165, 233, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-10-result-card span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #e0f2fe;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-10-result-card h4 {
  margin: 0 0 9px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 19px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-10-result-card p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 760px) {
  .vb-fetch-10-search,
  .vb-fetch-10-results {
    grid-template-columns: 1fr;
  }

  .vb-fetch-10-search button {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-10-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API search request example is useful for blog search, product search, user directories, documentation search, support center search, dashboard filtering, and API-powered result pages.

11. Fetch API Live Search with Debounce

A Fetch API live search with debounce prevents too many API requests while the user is typing. Instead of sending a request on every keypress, JavaScript waits until the user stops typing for a short moment.

This example uses a debounce function, automatic search, minimum keyword length, loading state, request cancellation with AbortController, and dynamic result rendering. It is a useful pattern for autocomplete search, product search, dashboard search, and API search forms.

Example 11

Live Search with Debounce

Type a keyword and let JavaScript wait before sending a Fetch API search request.

Waiting for input.
Start typing to search API data.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-11-demo");
  if (!demo) return;

  const input = demo.querySelector("[data-vb-fetch-11-input]");
  const meta = demo.querySelector("[data-vb-fetch-11-meta]");
  const results = demo.querySelector("[data-vb-fetch-11-results]");

  let debounceTimer = null;
  let controller = null;

  function setMeta(text, type) {
    meta.className = "vb-fetch-11-meta";

    if (type === "success") {
      meta.classList.add("is-success");
    }

    if (type === "error") {
      meta.classList.add("is-error");
    }

    meta.textContent = text;
  }

  function renderItems(items) {
    if (!items.length) {
      results.innerHTML = '<div class="vb-fetch-11-empty">No results found.</div>';
      setMeta("No matching results.", "error");
      return;
    }

    results.innerHTML = items.map(function (item) {
      return (
        '<div class="vb-fetch-11-item">' +
          '<strong>' + item.title + '</strong>' +
          '<span>Post ID #' + item.id + '</span>' +
        '</div>'
      );
    }).join("");

    setMeta("Showing " + items.length + " debounced API results.", "success");
  }

  async function runLiveSearch(query) {
    if (controller) {
      controller.abort();
    }

    controller = new AbortController();

    setMeta("Searching API for “" + query + "”...", "");
    results.innerHTML = '<div class="vb-fetch-11-empty">Loading results...</div>';

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=30", {
        signal: controller.signal
      });

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const posts = await response.json();
      const filtered = posts.filter(function (post) {
        return post.title.toLowerCase().includes(query) || post.body.toLowerCase().includes(query);
      }).slice(0, 7);

      renderItems(filtered);
    } catch (error) {
      if (error.name === "AbortError") {
        return;
      }

      results.innerHTML = '<div class="vb-fetch-11-empty">Request failed.</div>';
      setMeta("Live search failed: " + error.message, "error");
    }
  }

  input.addEventListener("input", function () {
    const query = input.value.trim().toLowerCase();

    clearTimeout(debounceTimer);

    if (query.length < 3) {
      if (controller) {
        controller.abort();
      }

      results.innerHTML = '<div class="vb-fetch-11-empty">Type at least 3 characters to search.</div>';
      setMeta("Waiting for at least 3 characters.", "");
      return;
    }

    setMeta("Typing detected. Waiting 500ms before request...", "");

    debounceTimer = setTimeout(function () {
      runLiveSearch(query);
    }, 500);
  });
})();

HTML

<div class="vb-fetch-11-demo">
  <div class="vb-fetch-11-layout">
    <div class="vb-fetch-11-copy">
      <span>Example 11</span>
      <h3>Live Search with Debounce</h3>
      <p>Type a keyword and let JavaScript wait before sending a Fetch API search request.</p>
    </div>

    <div class="vb-fetch-11-app">
      <label>
        Live search posts
        <input type="search" value="" placeholder="Type at least 3 characters..." data-vb-fetch-11-input>
      </label>

      <div class="vb-fetch-11-meta" data-vb-fetch-11-meta>
        Waiting for input.
      </div>

      <div class="vb-fetch-11-results" data-vb-fetch-11-results>
        <div class="vb-fetch-11-empty">Start typing to search API data.</div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-11-demo,
.vb-fetch-11-demo * {
  box-sizing: border-box;
}

.vb-fetch-11-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(168, 85, 247, 0.17), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(6, 182, 212, 0.17), transparent 34%),
    linear-gradient(135deg, #faf5ff 0%, #ecfeff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(168, 85, 247, 0.16);
  overflow: hidden;
}

.vb-fetch-11-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: 24px;
  max-width: 1120px;
  margin: 0 auto;
  min-width: 0;
}

.vb-fetch-11-copy {
  min-width: 0;
  padding: clamp(22px, 4vw, 36px);
  border-radius: 30px;
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #581c87 0%, #9333ea 52%, #0891b2 100%) !important;
  box-shadow: 0 30px 90px rgba(147, 51, 234, 0.20);
}

.vb-fetch-11-copy span {
  display: inline-flex;
  margin-bottom: 15px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #f3e8ff !important;
  -webkit-text-fill-color: #f3e8ff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-11-copy h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-11-copy p {
  margin: 0 !important;
  color: #cffafe !important;
  -webkit-text-fill-color: #cffafe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-11-app {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.10);
}

.vb-fetch-11-app label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #6b21a8 !important;
  -webkit-text-fill-color: #6b21a8 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-11-app input {
  width: 100%;
  min-width: 0;
  min-height: 54px;
  padding: 0 16px;
  border: 1px solid #e9d5ff;
  border-radius: 18px;
  background: #faf5ff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px;
  font-weight: 800;
  outline: none;
}

.vb-fetch-11-meta {
  min-width: 0;
  padding: 13px 15px;
  border-radius: 16px;
  background: #f5f3ff;
  border: 1px solid #ddd6fe;
  color: #5b21b6 !important;
  -webkit-text-fill-color: #5b21b6 !important;
  font-size: 14px;
  line-height: 1.45;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-11-meta.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-11-meta.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-11-results {
  display: grid;
  gap: 10px;
  min-width: 0;
  max-height: 360px;
  overflow: auto;
  padding: 4px;
}

.vb-fetch-11-item,
.vb-fetch-11-empty {
  min-width: 0;
  padding: 16px;
  border-radius: 18px;
  background:
    radial-gradient(circle at 14% 12%, rgba(168, 85, 247, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.20);
}

.vb-fetch-11-item strong {
  display: block;
  margin-bottom: 5px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px;
  line-height: 1.3;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-11-item span,
.vb-fetch-11-empty {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 700;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-fetch-11-layout {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-11-copy h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API live search with debounce example is useful for autocomplete interfaces, product search, dashboard search, documentation search, support center search, and API-powered instant search fields.

12. Fetch API Pagination Example

Fetch API pagination is used when an API returns data in pages instead of loading everything at once. This is important for blogs, products, orders, comments, users, tickets, dashboards, and any large dataset.

This example uses page state, previous and next buttons, query parameters, loading state, disabled button logic, and API result rendering. It loads different result pages with _page and _limit query parameters.

Example 12

Fetch API Pagination

Load paginated API results with previous and next buttons using query parameters.

Page 1
Loading first page…

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-12-demo");
  if (!demo) return;

  const prevButton = demo.querySelector("[data-vb-fetch-12-prev]");
  const nextButton = demo.querySelector("[data-vb-fetch-12-next]");
  const pageLabel = demo.querySelector("[data-vb-fetch-12-page]");
  const status = demo.querySelector("[data-vb-fetch-12-status]");
  const list = demo.querySelector("[data-vb-fetch-12-list]");

  let currentPage = 1;
  const limit = 4;
  const maxPage = 5;

  function setStatus(text, type) {
    status.className = "vb-fetch-12-status";

    if (type === "success") {
      status.classList.add("is-success");
    }

    if (type === "error") {
      status.classList.add("is-error");
    }

    status.textContent = text;
  }

  function updateButtons() {
    prevButton.disabled = currentPage <= 1;
    nextButton.disabled = currentPage >= maxPage;
    pageLabel.textContent = "Page " + currentPage + " of " + maxPage;
  }

  function renderPage(posts) {
    list.innerHTML = posts.map(function (post) {
      return (
        '<article class="vb-fetch-12-row">' +
          '<span>' + post.id + '</span>' +
          '<div>' +
            '<strong>' + post.title + '</strong>' +
            '<p>' + post.body + '</p>' +
          '</div>' +
        '</article>'
      );
    }).join("");
  }

  async function loadPage() {
    updateButtons();
    list.innerHTML = "";
    setStatus("Loading page " + currentPage + "...", "");

    const params = new URLSearchParams({
      _page: String(currentPage),
      _limit: String(limit)
    });

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts?" + params.toString());

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const posts = await response.json();

      renderPage(posts);
      setStatus("Loaded " + posts.length + " records for page " + currentPage + ".", "success");
    } catch (error) {
      list.innerHTML = "";
      setStatus("Pagination request failed: " + error.message, "error");
    }

    updateButtons();
  }

  prevButton.addEventListener("click", function () {
    if (currentPage > 1) {
      currentPage -= 1;
      loadPage();
    }
  });

  nextButton.addEventListener("click", function () {
    if (currentPage < maxPage) {
      currentPage += 1;
      loadPage();
    }
  });

  loadPage();
})();

HTML

<div class="vb-fetch-12-demo">
  <div class="vb-fetch-12-shell">
    <div class="vb-fetch-12-header">
      <span>Example 12</span>
      <h3>Fetch API Pagination</h3>
      <p>Load paginated API results with previous and next buttons using query parameters.</p>
    </div>

    <div class="vb-fetch-12-controls">
      <button type="button" data-vb-fetch-12-prev>Previous</button>
      <div data-vb-fetch-12-page>Page 1</div>
      <button type="button" data-vb-fetch-12-next>Next</button>
    </div>

    <div class="vb-fetch-12-status" data-vb-fetch-12-status>
      Loading first page...
    </div>

    <div class="vb-fetch-12-list" data-vb-fetch-12-list></div>
  </div>
</div>

CSS

.vb-fetch-12-demo,
.vb-fetch-12-demo * {
  box-sizing: border-box;
}

.vb-fetch-12-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(245, 158, 11, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(37, 99, 235, 0.16), transparent 34%),
    linear-gradient(135deg, #fffbeb 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(245, 158, 11, 0.16);
  overflow: hidden;
}

.vb-fetch-12-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-12-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #78350f 0%, #f59e0b 52%, #2563eb 100%) !important;
}

.vb-fetch-12-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.16);
  color: #fef3c7 !important;
  -webkit-text-fill-color: #fef3c7 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-12-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-12-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-12-controls {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
}

.vb-fetch-12-controls button {
  min-height: 48px;
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #f59e0b, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(245, 158, 11, 0.20);
}

.vb-fetch-12-controls button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.vb-fetch-12-controls div {
  min-width: 0;
  min-height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  border-radius: 999px;
  background: #fffbeb;
  border: 1px solid #fde68a;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 14px;
  font-weight: 950;
}

.vb-fetch-12-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #fffbeb;
  border: 1px solid #fde68a;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-12-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-12-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-12-list {
  display: grid;
  gap: 12px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-12-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 12% 12%, rgba(245, 158, 11, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-12-row span {
  display: flex;
  width: 42px;
  height: 42px;
  align-items: center;
  justify-content: center;
  border-radius: 15px;
  background: linear-gradient(135deg, #f59e0b, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-fetch-12-row strong {
  display: block;
  margin-bottom: 5px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 17px;
  line-height: 1.3;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-12-row p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  .vb-fetch-12-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-12-controls {
    grid-template-columns: 1fr;
  }

  .vb-fetch-12-controls button,
  .vb-fetch-12-controls div {
    width: 100%;
  }
}

This Fetch API pagination example is useful for blog lists, product catalogs, order tables, user directories, comment systems, dashboard records, support tickets, and API result pages with many records.

13. Fetch API Load More Button

A Fetch API load more button is useful when you want to load API results in smaller batches instead of showing everything at once. This pattern is common in blog feeds, product lists, portfolios, comments, dashboards, user lists, and search results.

This example keeps track of the current page, requests the next batch with query parameters, appends new results to the existing list, disables the button while loading, and hides the button when there are no more demo pages.

Example 13

Fetch API Load More Button

Load the next batch of API records and append them to the existing list.

Click “Load More” to request the first batch.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-13-demo");
  if (!demo) return;

  const status = demo.querySelector("[data-vb-fetch-13-status]");
  const grid = demo.querySelector("[data-vb-fetch-13-grid]");
  const button = demo.querySelector("[data-vb-fetch-13-load]");

  let page = 0;
  const limit = 3;
  const maxPage = 4;

  function renderCards(posts) {
    const markup = posts.map(function (post) {
      return (
        '<article class="vb-fetch-13-card">' +
          '<span>Post #' + post.id + '</span>' +
          '<h4>' + post.title + '</h4>' +
          '<p>' + post.body + '</p>' +
        '</article>'
      );
    }).join("");

    grid.insertAdjacentHTML("beforeend", markup);
  }

  async function loadMore() {
    if (page >= maxPage) return;

    page += 1;
    button.disabled = true;
    button.textContent = "Loading...";
    status.className = "vb-fetch-13-status";
    status.textContent = "Loading batch " + page + " from the API...";

    const params = new URLSearchParams({
      _page: String(page),
      _limit: String(limit)
    });

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts?" + params.toString());

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const posts = await response.json();
      renderCards(posts);

      status.textContent = "Batch " + page + " loaded. Total visible cards: " + grid.children.length + ".";

      if (page >= maxPage) {
        button.textContent = "No More Results";
        button.disabled = true;
      } else {
        button.textContent = "Load More";
        button.disabled = false;
      }
    } catch (error) {
      page -= 1;
      status.className = "vb-fetch-13-status is-error";
      status.textContent = "Load more request failed: " + error.message;
      button.textContent = "Try Again";
      button.disabled = false;
    }
  }

  button.addEventListener("click", loadMore);
})();

HTML

<div class="vb-fetch-13-demo">
  <div class="vb-fetch-13-shell">
    <div class="vb-fetch-13-header">
      <span>Example 13</span>
      <h3>Fetch API Load More Button</h3>
      <p>Load the next batch of API records and append them to the existing list.</p>
    </div>

    <div class="vb-fetch-13-status" data-vb-fetch-13-status>
      Click “Load More” to request the first batch.
    </div>

    <div class="vb-fetch-13-grid" data-vb-fetch-13-grid></div>

    <div class="vb-fetch-13-footer">
      <button type="button" data-vb-fetch-13-load>Load More</button>
    </div>
  </div>
</div>

CSS

.vb-fetch-13-demo,
.vb-fetch-13-demo * {
  box-sizing: border-box;
}

.vb-fetch-13-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 197, 94, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(245, 158, 11, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #fffbeb 54%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.18);
  overflow: hidden;
}

.vb-fetch-13-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-13-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #14532d 0%, #16a34a 52%, #f59e0b 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-13-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-13-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-13-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #fef3c7 !important;
  -webkit-text-fill-color: #fef3c7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-13-status {
  margin: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-13-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-13-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-13-card {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(34, 197, 94, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-13-card span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-13-card h4 {
  margin: 0 0 9px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.28 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-13-card p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-13-footer {
  display: flex;
  justify-content: center;
  padding: 0 clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px);
}

.vb-fetch-13-footer button {
  min-height: 52px;
  padding: 0 26px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #16a34a, #f59e0b);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(34, 197, 94, 0.22);
}

.vb-fetch-13-footer button:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

@media (max-width: 980px) {
  .vb-fetch-13-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .vb-fetch-13-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-13-grid {
    grid-template-columns: 1fr;
  }

  .vb-fetch-13-footer button {
    width: 100%;
  }
}

This Fetch API load more button example is useful for blog feeds, product lists, portfolio grids, comment sections, user directories, dashboard records, and API-powered result pages.

14. Fetch API Infinite Scroll

Fetch API infinite scroll loads more data automatically when the user reaches the bottom of a list. This pattern is common in social feeds, product feeds, article lists, image feeds, dashboards, and mobile-first browsing experiences.

This example uses IntersectionObserver to watch a sentinel element. When the sentinel becomes visible, JavaScript requests the next API page and appends new items to the feed.

Example 14

Fetch API Infinite Scroll

Automatically load more API data when the scroll sentinel becomes visible.

Scroll here to load more API items...

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-14-demo");
  if (!demo) return;

  const shell = demo.querySelector(".vb-fetch-14-shell");
  const feed = demo.querySelector("[data-vb-fetch-14-feed]");
  const sentinel = demo.querySelector("[data-vb-fetch-14-sentinel]");

  let page = 0;
  const limit = 4;
  const maxPage = 4;
  let loading = false;

  function renderItems(items) {
    const html = items.map(function (item) {
      return (
        '<article class="vb-fetch-14-item">' +
          '<div class="vb-fetch-14-number">' + item.id + '</div>' +
          '<div>' +
            '<strong>' + item.title + '</strong>' +
            '<p>' + item.body + '</p>' +
          '</div>' +
        '</article>'
      );
    }).join("");

    feed.insertAdjacentHTML("beforeend", html);
  }

  async function loadNextPage() {
    if (loading || page >= maxPage) return;

    loading = true;
    page += 1;
    sentinel.className = "vb-fetch-14-sentinel";
    sentinel.textContent = "Loading page " + page + "...";

    const params = new URLSearchParams({
      _page: String(page),
      _limit: String(limit)
    });

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts?" + params.toString());

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const items = await response.json();
      renderItems(items);

      if (page >= maxPage) {
        sentinel.className = "vb-fetch-14-sentinel is-done";
        sentinel.textContent = "All demo pages loaded.";
        observer.disconnect();
      } else {
        sentinel.textContent = "Scroll here to load more API items...";
      }
    } catch (error) {
      page -= 1;
      sentinel.className = "vb-fetch-14-sentinel is-error";
      sentinel.textContent = "Infinite scroll request failed: " + error.message;
    } finally {
      loading = false;
    }
  }

  const observer = new IntersectionObserver(function (entries) {
    if (entries[0].isIntersecting) {
      loadNextPage();
    }
  }, {
    root: shell,
    threshold: 0.6
  });

  observer.observe(sentinel);
  loadNextPage();
})();

HTML

<div class="vb-fetch-14-demo">
  <div class="vb-fetch-14-shell">
    <div class="vb-fetch-14-header">
      <span>Example 14</span>
      <h3>Fetch API Infinite Scroll</h3>
      <p>Automatically load more API data when the scroll sentinel becomes visible.</p>
    </div>

    <div class="vb-fetch-14-feed" data-vb-fetch-14-feed></div>

    <div class="vb-fetch-14-sentinel" data-vb-fetch-14-sentinel>
      Scroll here to load more API items...
    </div>
  </div>
</div>

CSS

.vb-fetch-14-demo,
.vb-fetch-14-demo * {
  box-sizing: border-box;
}

.vb-fetch-14-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(99, 102, 241, 0.17), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(236, 72, 153, 0.14), transparent 34%),
    linear-gradient(135deg, #eef2ff 0%, #fdf2f8 54%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.16);
  overflow: hidden;
}

.vb-fetch-14-shell {
  max-width: 1120px;
  max-height: 720px;
  margin: 0 auto;
  overflow: auto;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-14-header {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #312e81 0%, #6366f1 52%, #db2777 100%) !important;
}

.vb-fetch-14-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-14-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 60px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-14-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-14-feed {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-14-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 12% 12%, rgba(99, 102, 241, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-14-number {
  display: flex;
  width: 46px;
  height: 46px;
  align-items: center;
  justify-content: center;
  border-radius: 16px;
  background: linear-gradient(135deg, #6366f1, #db2777);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-fetch-14-item strong {
  display: block;
  margin-bottom: 6px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 17px;
  line-height: 1.3;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-14-item p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-14-sentinel {
  margin: 0 clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px);
  min-height: 58px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 18px;
  background: #eef2ff;
  border: 1px dashed #a5b4fc;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 900;
  text-align: center;
}

.vb-fetch-14-sentinel.is-done {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-14-sentinel.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

@media (max-width: 640px) {
  .vb-fetch-14-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-14-item {
    grid-template-columns: 1fr;
  }
}

This Fetch API infinite scroll example is useful for social feeds, product feeds, blog lists, dashboard activity feeds, image feeds, mobile browsing interfaces, and API-powered content streams.

15. Fetch API Filter Dropdown

A Fetch API filter dropdown lets users change the API request based on a selected option. This pattern is useful for product categories, post types, user roles, order status, task status, support tickets, locations, and dashboard filters.

This example uses a dropdown to choose a user ID, builds query parameters with URLSearchParams, sends a Fetch API request, and renders only the records that match the selected filter.

Example 15

Fetch API Filter Dropdown

Select a filter option and load matching API records with query parameters.

Loading filtered results...

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-15-demo");
  if (!demo) return;

  const select = demo.querySelector("[data-vb-fetch-15-select]");
  const status = demo.querySelector("[data-vb-fetch-15-status]");
  const grid = demo.querySelector("[data-vb-fetch-15-grid]");

  function setStatus(text, type) {
    status.className = "vb-fetch-15-status";

    if (type === "success") {
      status.classList.add("is-success");
    }

    if (type === "error") {
      status.classList.add("is-error");
    }

    status.textContent = text;
  }

  function renderCards(posts) {
    grid.innerHTML = posts.map(function (post) {
      return (
        '<article class="vb-fetch-15-card">' +
          '<span>User #' + post.userId + ' · Post #' + post.id + '</span>' +
          '<h4>' + post.title + '</h4>' +
          '<p>' + post.body + '</p>' +
        '</article>'
      );
    }).join("");
  }

  async function loadFilteredPosts() {
    const userId = select.value;

    grid.innerHTML = "";
    setStatus("Loading posts for user #" + userId + "...", "");

    const params = new URLSearchParams({
      userId: userId,
      _limit: "6"
    });

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts?" + params.toString());

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const posts = await response.json();

      renderCards(posts);
      setStatus("Loaded " + posts.length + " filtered posts for user #" + userId + ".", "success");
    } catch (error) {
      grid.innerHTML = "";
      setStatus("Filter request failed: " + error.message, "error");
    }
  }

  select.addEventListener("change", loadFilteredPosts);
  loadFilteredPosts();
})();

HTML

<div class="vb-fetch-15-demo">
  <div class="vb-fetch-15-shell">
    <div class="vb-fetch-15-header">
      <span>Example 15</span>
      <h3>Fetch API Filter Dropdown</h3>
      <p>Select a filter option and load matching API records with query parameters.</p>
    </div>

    <div class="vb-fetch-15-filter">
      <label>
        Filter posts by user
        <select data-vb-fetch-15-select>
          <option value="1">User 1</option>
          <option value="2">User 2</option>
          <option value="3">User 3</option>
          <option value="4">User 4</option>
        </select>
      </label>
    </div>

    <div class="vb-fetch-15-status" data-vb-fetch-15-status>
      Loading filtered results...
    </div>

    <div class="vb-fetch-15-grid" data-vb-fetch-15-grid></div>
  </div>
</div>

CSS

.vb-fetch-15-demo,
.vb-fetch-15-demo * {
  box-sizing: border-box;
}

.vb-fetch-15-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(20, 184, 166, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdfa 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.16);
  overflow: hidden;
}

.vb-fetch-15-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-15-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #115e59 0%, #14b8a6 52%, #2563eb 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-15-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-15-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-15-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-15-filter {
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
}

.vb-fetch-15-filter label {
  display: grid;
  max-width: 420px;
  gap: 8px;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-15-filter select {
  width: 100%;
  min-height: 52px;
  padding: 0 16px;
  border: 1px solid #99f6e4;
  border-radius: 18px;
  background: #f0fdfa;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px;
  font-weight: 850;
  outline: none;
}

.vb-fetch-15-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #f0fdfa;
  border: 1px solid #99f6e4;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-15-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-15-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-15-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-15-card {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(20, 184, 166, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-15-card span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-15-card h4 {
  margin: 0 0 9px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 19px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-15-card p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 760px) {
  .vb-fetch-15-grid {
    grid-template-columns: 1fr;
  }

  .vb-fetch-15-filter label {
    max-width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-15-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API filter dropdown example is useful for product categories, user roles, order statuses, support tickets, dashboard records, task filters, location filters, and API-powered admin screens.

16. Fetch API with Query Parameters

Fetch API query parameters are used when you need to send options through the URL, such as search keywords, page numbers, result limits, categories, sorting values, filters, or user IDs. The cleanest way to build these URLs in JavaScript is with URLSearchParams.

This example lets users choose a user ID and result limit. JavaScript builds the API URL with URLSearchParams, sends the Fetch API request, shows the final generated URL, and renders the matching records.

Example 16

Fetch API with Query Parameters

Build a dynamic API URL with URLSearchParams and load filtered results.

Generated URL will appear here.
Choose parameters and load API data.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-16-demo");
  if (!demo) return;

  const userSelect = demo.querySelector("[data-vb-fetch-16-user]");
  const limitSelect = demo.querySelector("[data-vb-fetch-16-limit]");
  const button = demo.querySelector("[data-vb-fetch-16-load]");
  const urlBox = demo.querySelector("[data-vb-fetch-16-url]");
  const status = demo.querySelector("[data-vb-fetch-16-status]");
  const grid = demo.querySelector("[data-vb-fetch-16-grid]");

  function setStatus(text, type) {
    status.className = "vb-fetch-16-status";

    if (type === "success") {
      status.classList.add("is-success");
    }

    if (type === "error") {
      status.classList.add("is-error");
    }

    status.textContent = text;
  }

  function renderPosts(posts) {
    grid.innerHTML = posts.map(function (post) {
      return (
        '<article class="vb-fetch-16-card">' +
          '<span>User #' + post.userId + ' · ID #' + post.id + '</span>' +
          '<h4>' + post.title + '</h4>' +
          '<p>' + post.body + '</p>' +
        '</article>'
      );
    }).join("");
  }

  async function loadWithParams() {
    const params = new URLSearchParams({
      userId: userSelect.value,
      _limit: limitSelect.value
    });

    const url = "https://jsonplaceholder.typicode.com/posts?" + params.toString();

    button.disabled = true;
    urlBox.textContent = url;
    grid.innerHTML = "";
    setStatus("Loading API data with query parameters...", "");

    try {
      const response = await fetch(url);

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const posts = await response.json();

      renderPosts(posts);
      setStatus("Loaded " + posts.length + " records using URLSearchParams.", "success");
    } catch (error) {
      setStatus("Query parameter request failed: " + error.message, "error");
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", loadWithParams);
  loadWithParams();
})();

HTML

<div class="vb-fetch-16-demo">
  <div class="vb-fetch-16-shell">
    <div class="vb-fetch-16-header">
      <span>Example 16</span>
      <h3>Fetch API with Query Parameters</h3>
      <p>Build a dynamic API URL with URLSearchParams and load filtered results.</p>
    </div>

    <div class="vb-fetch-16-controls">
      <label>
        User ID
        <select data-vb-fetch-16-user>
          <option value="1">User 1</option>
          <option value="2">User 2</option>
          <option value="3">User 3</option>
          <option value="4">User 4</option>
        </select>
      </label>

      <label>
        Result limit
        <select data-vb-fetch-16-limit>
          <option value="3">3 results</option>
          <option value="5" selected>5 results</option>
          <option value="8">8 results</option>
        </select>
      </label>

      <button type="button" data-vb-fetch-16-load>Load Results</button>
    </div>

    <div class="vb-fetch-16-url" data-vb-fetch-16-url>
      Generated URL will appear here.
    </div>

    <div class="vb-fetch-16-status" data-vb-fetch-16-status>
      Choose parameters and load API data.
    </div>

    <div class="vb-fetch-16-grid" data-vb-fetch-16-grid></div>
  </div>
</div>

CSS

.vb-fetch-16-demo,
.vb-fetch-16-demo * {
  box-sizing: border-box;
}

.vb-fetch-16-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(37, 99, 235, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(14, 165, 233, 0.17), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #ecfeff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.16);
  overflow: hidden;
}

.vb-fetch-16-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-16-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #1e3a8a 0%, #2563eb 52%, #0891b2 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-16-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-16-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-16-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #cffafe !important;
  -webkit-text-fill-color: #cffafe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-16-controls {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)) auto;
  gap: 14px;
  align-items: end;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
}

.vb-fetch-16-controls label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-16-controls select {
  width: 100%;
  min-width: 0;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid #bfdbfe;
  border-radius: 18px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 850;
  outline: none;
}

.vb-fetch-16-controls button {
  min-height: 52px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #0891b2);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.24);
}

.vb-fetch-16-controls button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-16-url,
.vb-fetch-16-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-16-url {
  background: #0f172a;
  border: 1px solid #1e293b;
  color: #bfdbfe !important;
  -webkit-text-fill-color: #bfdbfe !important;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

.vb-fetch-16-status {
  background: #eff6ff;
  border: 1px solid #bfdbfe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
}

.vb-fetch-16-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-16-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-16-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-16-card {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(37, 99, 235, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-16-card span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-16-card h4 {
  margin: 0 0 9px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 19px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-16-card p {
  margin: 0 !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 820px) {
  .vb-fetch-16-controls,
  .vb-fetch-16-grid {
    grid-template-columns: 1fr;
  }

  .vb-fetch-16-controls button {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-16-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API query parameters example is useful for search pages, product filters, blog archives, dashboards, user directories, order filters, category pages, and paginated API result interfaces.

17. Fetch API Weather Card Example

A Fetch API weather card shows how API data can be transformed into a user-friendly widget. Many real weather APIs require API keys, so this demo uses a local mock weather API function with the same async Fetch API-style flow.

This example focuses on request-like behavior: selecting a city, showing loading state, waiting for async data, handling invalid city values, rendering weather details, and updating the card UI based on the returned response object.

Example 17

Fetch API Weather Card

Load weather-style API data and render a polished weather dashboard card.

Weather API

Ready to load

Select a city and load weather data.

--°Temperature
--%Humidity
-- km/hWind

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-17-demo");
  if (!demo) return;

  const citySelect = demo.querySelector("[data-vb-fetch-17-city]");
  const button = demo.querySelector("[data-vb-fetch-17-load]");
  const card = demo.querySelector("[data-vb-fetch-17-card]");

  const weatherData = {
    Tallinn: { temp: 18, humidity: 61, wind: 14, condition: "Cloudy with light wind" },
    London: { temp: 16, humidity: 72, wind: 18, condition: "Light rain expected" },
    "New York": { temp: 24, humidity: 58, wind: 11, condition: "Bright and partly sunny" },
    Tokyo: { temp: 28, humidity: 66, wind: 9, condition: "Warm evening weather" }
  };

  function fakeWeatherFetch(city) {
    return new Promise(function (resolve, reject) {
      setTimeout(function () {
        if (!weatherData[city]) {
          reject(new Error("City not found"));
          return;
        }

        resolve({
          ok: true,
          json: function () {
            return Promise.resolve({
              city: city,
              current: weatherData[city]
            });
          }
        });
      }, 850);
    });
  }

  function renderWeather(data) {
    const weather = data.current;

    card.className = "vb-fetch-17-card";
    card.innerHTML =
      '<span>Weather API</span>' +
      '<h4>' + data.city + '</h4>' +
      '<p>' + weather.condition + '</p>' +
      '<div class="vb-fetch-17-stats">' +
        '<div><strong>' + weather.temp + '°C</strong><small>Temperature</small></div>' +
        '<div><strong>' + weather.humidity + '%</strong><small>Humidity</small></div>' +
        '<div><strong>' + weather.wind + ' km/h</strong><small>Wind</small></div>' +
      '</div>';
  }

  async function loadWeather() {
    const city = citySelect.value;

    button.disabled = true;
    button.textContent = "Loading...";

    card.className = "vb-fetch-17-card";
    card.innerHTML =
      '<span>Loading</span>' +
      '<h4>' + city + '</h4>' +
      '<p>Requesting weather-style API data...</p>' +
      '<div class="vb-fetch-17-stats">' +
        '<div><strong>--°</strong><small>Temperature</small></div>' +
        '<div><strong>--%</strong><small>Humidity</small></div>' +
        '<div><strong>-- km/h</strong><small>Wind</small></div>' +
      '</div>';

    try {
      const response = await fakeWeatherFetch(city);

      if (!response.ok) {
        throw new Error("Weather API request failed");
      }

      const data = await response.json();
      renderWeather(data);
    } catch (error) {
      card.className = "vb-fetch-17-card is-error";
      card.innerHTML =
        '<span>Error</span>' +
        '<h4>Weather unavailable</h4>' +
        '<p>' + error.message + '</p>' +
        '<div class="vb-fetch-17-stats">' +
          '<div><strong>--</strong><small>No data</small></div>' +
          '<div><strong>--</strong><small>No data</small></div>' +
          '<div><strong>--</strong><small>No data</small></div>' +
        '</div>';
    } finally {
      button.disabled = false;
      button.textContent = "Load Weather";
    }
  }

  button.addEventListener("click", loadWeather);
})();

HTML

<div class="vb-fetch-17-demo">
  <div class="vb-fetch-17-shell">
    <div class="vb-fetch-17-header">
      <span>Example 17</span>
      <h3>Fetch API Weather Card</h3>
      <p>Load weather-style API data and render a polished weather dashboard card.</p>
    </div>

    <div class="vb-fetch-17-body">
      <div class="vb-fetch-17-controls">
        <label>
          Choose city
          <select data-vb-fetch-17-city>
            <option value="Tallinn">Tallinn</option>
            <option value="London">London</option>
            <option value="New York">New York</option>
            <option value="Tokyo">Tokyo</option>
          </select>
        </label>

        <button type="button" data-vb-fetch-17-load>Load Weather</button>
      </div>

      <div class="vb-fetch-17-card" data-vb-fetch-17-card>
        <span>Weather API</span>
        <h4>Ready to load</h4>
        <p>Select a city and load weather data.</p>
        <div class="vb-fetch-17-stats">
          <div><strong>--°</strong><small>Temperature</small></div>
          <div><strong>--%</strong><small>Humidity</small></div>
          <div><strong>-- km/h</strong><small>Wind</small></div>
        </div>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-17-demo,
.vb-fetch-17-demo * {
  box-sizing: border-box;
}

.vb-fetch-17-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(14, 165, 233, 0.18), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(234, 179, 8, 0.16), transparent 34%),
    linear-gradient(135deg, #f0f9ff 0%, #fefce8 54%, #ffffff 100%) !important;
  border: 1px solid rgba(14, 165, 233, 0.16);
  overflow: hidden;
}

.vb-fetch-17-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-17-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.18), transparent 34%),
    linear-gradient(135deg, #075985 0%, #0284c7 52%, #eab308 100%) !important;
}

.vb-fetch-17-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.16);
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-17-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-17-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #fef9c3 !important;
  -webkit-text-fill-color: #fef9c3 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-17-body {
  display: grid;
  grid-template-columns: minmax(260px, 0.8fr) minmax(0, 1.2fr);
  gap: 22px;
  padding: clamp(20px, 4vw, 34px);
  min-width: 0;
}

.vb-fetch-17-controls {
  display: grid;
  align-content: start;
  gap: 14px;
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background: #f0f9ff;
  border: 1px solid #bae6fd;
}

.vb-fetch-17-controls label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #0369a1 !important;
  -webkit-text-fill-color: #0369a1 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-17-controls select {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid #bae6fd;
  border-radius: 18px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 850;
  outline: none;
}

.vb-fetch-17-controls button {
  min-height: 52px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #0284c7, #eab308);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(14, 165, 233, 0.22);
}

.vb-fetch-17-controls button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-17-card {
  min-width: 0;
  padding: clamp(22px, 4vw, 34px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 16% 14%, rgba(255,255,255,0.28), transparent 34%),
    linear-gradient(135deg, #0284c7 0%, #06b6d4 50%, #facc15 100%) !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  box-shadow: 0 24px 70px rgba(14, 165, 233, 0.22);
}

.vb-fetch-17-card.is-error {
  background: linear-gradient(135deg, #991b1b, #ef4444) !important;
}

.vb-fetch-17-card span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(15, 23, 42, 0.18);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.vb-fetch-17-card h4 {
  margin: 0 0 10px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 58px) !important;
  line-height: 0.98 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-fetch-17-card p {
  margin: 0 0 22px !important;
  color: #f8fafc !important;
  -webkit-text-fill-color: #f8fafc !important;
  font-size: 16px;
  line-height: 1.6;
  font-weight: 750;
  overflow-wrap: anywhere;
}

.vb-fetch-17-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.vb-fetch-17-stats div {
  min-width: 0;
  padding: 16px;
  border-radius: 20px;
  background: rgba(255,255,255,0.18);
  border: 1px solid rgba(255,255,255,0.20);
  backdrop-filter: blur(10px);
}

.vb-fetch-17-stats strong {
  display: block;
  margin-bottom: 5px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 26px;
  line-height: 1;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-17-stats small {
  display: block;
  color: #f8fafc !important;
  -webkit-text-fill-color: #f8fafc !important;
  font-size: 12px;
  line-height: 1.3;
  font-weight: 800;
}

@media (max-width: 840px) {
  .vb-fetch-17-body {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-17-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-17-stats {
    grid-template-columns: 1fr;
  }
}

This weather card example is useful for API widgets, dashboard cards, weather-style layouts, location widgets, async UI states, and frontend components that need to transform API data into a clean visual card.

18. Fetch API Product List Example

A Fetch API product list is a practical pattern for ecommerce interfaces, product catalogs, price lists, dashboards, inventory tools, and API-powered landing pages. JavaScript loads product data, renders product cards, and shows useful product details in the UI.

This example uses a local mock product API with the same async structure as a real Fetch API request. It includes loading state, product cards, category labels, price formatting with Intl.NumberFormat, and a refresh button.

Example 18

Fetch API Product List

Load product-style API data and render ecommerce cards with formatted prices.

Ready to load product data.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-18-demo");
  if (!demo) return;

  const button = demo.querySelector("[data-vb-fetch-18-load]");
  const status = demo.querySelector("[data-vb-fetch-18-status]");
  const grid = demo.querySelector("[data-vb-fetch-18-grid]");

  const formatter = new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: "USD"
  });

  const products = [
    { name: "API Dashboard Kit", category: "SaaS", price: 79, stock: 12, description: "Reusable dashboard UI for API data." },
    { name: "Product Feed Template", category: "Ecommerce", price: 49, stock: 22, description: "Card layout for dynamic product lists." },
    { name: "Search API Module", category: "Tools", price: 39, stock: 8, description: "Frontend search block for API projects." },
    { name: "Admin Panel Starter", category: "Admin", price: 99, stock: 5, description: "Clean admin interface with API states." },
    { name: "Landing API Widget", category: "Marketing", price: 29, stock: 31, description: "Small API-powered widget for landing pages." },
    { name: "Inventory Grid UI", category: "Business", price: 59, stock: 16, description: "Product grid for internal inventory tools." }
  ];

  function fakeProductFetch() {
    return new Promise(function (resolve) {
      setTimeout(function () {
        resolve({
          ok: true,
          json: function () {
            return Promise.resolve(products);
          }
        });
      }, 750);
    });
  }

  function setStatus(text, type) {
    status.className = "";

    if (type === "success") {
      status.classList.add("is-success");
    }

    if (type === "error") {
      status.classList.add("is-error");
    }

    status.textContent = text;
  }

  function renderProducts(items) {
    grid.innerHTML = items.map(function (product, index) {
      return (
        '<article class="vb-fetch-18-product">' +
          '<div class="vb-fetch-18-image"></div>' +
          '<div class="vb-fetch-18-product-body">' +
            '<span>' + product.category + '</span>' +
            '<h4>' + product.name + '</h4>' +
            '<p>' + product.description + '</p>' +
            '<div class="vb-fetch-18-price">' +
              '<strong>' + formatter.format(product.price) + '</strong>' +
              '<small>' + product.stock + ' in stock</small>' +
            '</div>' +
          '</div>' +
        '</article>'
      );
    }).join("");
  }

  async function loadProducts() {
    button.disabled = true;
    button.textContent = "Loading...";
    grid.innerHTML = "";
    setStatus("Loading product API data...", "");

    try {
      const response = await fakeProductFetch();

      if (!response.ok) {
        throw new Error("Product API request failed");
      }

      const data = await response.json();

      renderProducts(data);
      setStatus("Loaded " + data.length + " product cards from async API-style data.", "success");
    } catch (error) {
      grid.innerHTML = "";
      setStatus("Product list failed: " + error.message, "error");
    } finally {
      button.disabled = false;
      button.textContent = "Load Products";
    }
  }

  button.addEventListener("click", loadProducts);
})();

HTML

<div class="vb-fetch-18-demo">
  <div class="vb-fetch-18-shell">
    <div class="vb-fetch-18-header">
      <span>Example 18</span>
      <h3>Fetch API Product List</h3>
      <p>Load product-style API data and render ecommerce cards with formatted prices.</p>
    </div>

    <div class="vb-fetch-18-toolbar">
      <button type="button" data-vb-fetch-18-load>Load Products</button>
      <div data-vb-fetch-18-status>Ready to load product data.</div>
    </div>

    <div class="vb-fetch-18-grid" data-vb-fetch-18-grid></div>
  </div>
</div>

CSS

.vb-fetch-18-demo,
.vb-fetch-18-demo * {
  box-sizing: border-box;
}

.vb-fetch-18-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(236, 72, 153, 0.15), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #fdf2f8 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(236, 72, 153, 0.14);
  overflow: hidden;
}

.vb-fetch-18-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-18-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #831843 0%, #db2777 52%, #16a34a 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-18-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #fce7f3 !important;
  -webkit-text-fill-color: #fce7f3 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-18-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-18-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-18-toolbar {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  align-items: center;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
}

.vb-fetch-18-toolbar button {
  min-height: 52px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #db2777, #16a34a);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(219, 39, 119, 0.20);
}

.vb-fetch-18-toolbar button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-18-toolbar div {
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #fdf2f8;
  border: 1px solid #fbcfe8;
  color: #be185d !important;
  -webkit-text-fill-color: #be185d !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-18-toolbar div.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-18-toolbar div.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-18-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-18-product {
  min-width: 0;
  overflow: hidden;
  border-radius: 24px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 44px rgba(15, 23, 42, 0.08);
}

.vb-fetch-18-image {
  min-height: 150px;
  background:
    radial-gradient(circle at 30% 25%, rgba(255,255,255,0.38), transparent 34%),
    linear-gradient(135deg, #db2777, #16a34a) !important;
}

.vb-fetch-18-product-body {
  padding: 18px;
}

.vb-fetch-18-product-body span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-18-product-body h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px !important;
  line-height: 1.2 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-18-product-body p {
  margin: 0 0 14px !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-18-price {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.vb-fetch-18-price strong {
  color: #be185d !important;
  -webkit-text-fill-color: #be185d !important;
  font-size: 24px;
  line-height: 1;
  font-weight: 950;
}

.vb-fetch-18-price small {
  color: #16a34a !important;
  -webkit-text-fill-color: #16a34a !important;
  font-size: 12px;
  font-weight: 950;
}

@media (max-width: 980px) {
  .vb-fetch-18-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 720px) {
  .vb-fetch-18-toolbar {
    grid-template-columns: 1fr;
  }

  .vb-fetch-18-toolbar button {
    width: 100%;
  }

  .vb-fetch-18-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-18-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API product list example is useful for ecommerce product grids, price lists, inventory dashboards, landing page products, SaaS plan lists, catalog interfaces, and API-powered shop sections.

19. Fetch API User Profile Dashboard

A Fetch API user profile dashboard is useful when a website or app needs to load account data, profile details, company information, address data, and contact fields from an API. This pattern is common in SaaS dashboards, admin panels, CRM tools, user portals, and account pages.

This example loads a user profile from a public JSON API, extracts nested object data, renders a dashboard card, and displays company, email, phone, website, and address information in a clean layout.

Example 19

Fetch API User Profile Dashboard

Load a user profile from an API and render nested contact, company, and address data.

Choose a user and load profile data.
API
No profile loaded

Waiting for API request

The user profile dashboard will appear here.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-19-demo");
  if (!demo) return;

  const userSelect = demo.querySelector("[data-vb-fetch-19-user]");
  const button = demo.querySelector("[data-vb-fetch-19-load]");
  const status = demo.querySelector("[data-vb-fetch-19-status]");
  const profile = demo.querySelector("[data-vb-fetch-19-profile]");

  function setStatus(text, type) {
    status.className = "vb-fetch-19-status";

    if (type === "success") {
      status.classList.add("is-success");
    }

    if (type === "error") {
      status.classList.add("is-error");
    }

    status.textContent = text;
  }

  function getInitials(name) {
    return name.split(" ").map(function (part) {
      return part.charAt(0);
    }).join("").slice(0, 2).toUpperCase();
  }

  function renderProfile(user) {
    profile.innerHTML =
      '<div class="vb-fetch-19-avatar">' + getInitials(user.name) + '</div>' +
      '<div class="vb-fetch-19-main">' +
        '<span>' + user.company.name + '</span>' +
        '<h4>' + user.name + '</h4>' +
        '<p>' + user.company.catchPhrase + '</p>' +
        '<div class="vb-fetch-19-info-grid">' +
          '<div><strong>Email</strong><small>' + user.email + '</small></div>' +
          '<div><strong>Phone</strong><small>' + user.phone + '</small></div>' +
          '<div><strong>Website</strong><small>' + user.website + '</small></div>' +
          '<div><strong>Address</strong><small>' + user.address.street + ', ' + user.address.city + '</small></div>' +
        '</div>' +
      '</div>';
  }

  async function loadProfile() {
    const userId = userSelect.value;

    button.disabled = true;
    setStatus("Loading user profile #" + userId + "...", "");

    profile.innerHTML =
      '<div class="vb-fetch-19-avatar">...</div>' +
      '<div class="vb-fetch-19-main">' +
        '<span>Loading</span>' +
        '<h4>Fetching profile</h4>' +
        '<p>Waiting for API response...</p>' +
      '</div>';

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/users/" + userId);

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const user = await response.json();

      renderProfile(user);
      setStatus("User profile #" + userId + " loaded successfully.", "success");
    } catch (error) {
      setStatus("Profile request failed: " + error.message, "error");
      profile.innerHTML =
        '<div class="vb-fetch-19-avatar">!</div>' +
        '<div class="vb-fetch-19-main">' +
          '<span>Error</span>' +
          '<h4>Profile unavailable</h4>' +
          '<p>The API request failed. Try again later.</p>' +
        '</div>';
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", loadProfile);
})();

HTML

<div class="vb-fetch-19-demo">
  <div class="vb-fetch-19-shell">
    <div class="vb-fetch-19-header">
      <span>Example 19</span>
      <h3>Fetch API User Profile Dashboard</h3>
      <p>Load a user profile from an API and render nested contact, company, and address data.</p>
    </div>

    <div class="vb-fetch-19-controls">
      <label>
        Choose user
        <select data-vb-fetch-19-user>
          <option value="1">User 1</option>
          <option value="2">User 2</option>
          <option value="3">User 3</option>
          <option value="4">User 4</option>
        </select>
      </label>

      <button type="button" data-vb-fetch-19-load>Load Profile</button>
    </div>

    <div class="vb-fetch-19-status" data-vb-fetch-19-status>
      Choose a user and load profile data.
    </div>

    <div class="vb-fetch-19-profile" data-vb-fetch-19-profile>
      <div class="vb-fetch-19-avatar">API</div>
      <div class="vb-fetch-19-main">
        <span>No profile loaded</span>
        <h4>Waiting for API request</h4>
        <p>The user profile dashboard will appear here.</p>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-19-demo,
.vb-fetch-19-demo * {
  box-sizing: border-box;
}

.vb-fetch-19-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(79, 70, 229, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(14, 165, 233, 0.16), transparent 34%),
    linear-gradient(135deg, #eef2ff 0%, #f0f9ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(79, 70, 229, 0.16);
  overflow: hidden;
}

.vb-fetch-19-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-19-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #312e81 0%, #4f46e5 52%, #0284c7 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-19-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-19-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-19-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-19-controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  align-items: end;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
}

.vb-fetch-19-controls label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-19-controls select {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid #c7d2fe;
  border-radius: 18px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 850;
  outline: none;
}

.vb-fetch-19-controls button {
  min-height: 52px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #4f46e5, #0284c7);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(79, 70, 229, 0.22);
}

.vb-fetch-19-controls button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-19-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #eef2ff;
  border: 1px solid #c7d2fe;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-19-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-19-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-19-profile {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 20px;
  min-width: 0;
  margin: clamp(20px, 4vw, 34px);
  padding: clamp(20px, 4vw, 32px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 14% 12%, rgba(79, 70, 229, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.08);
}

.vb-fetch-19-avatar {
  display: flex;
  width: 86px;
  height: 86px;
  align-items: center;
  justify-content: center;
  border-radius: 28px;
  background: linear-gradient(135deg, #4f46e5, #0284c7);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 24px;
  font-weight: 950;
  letter-spacing: -0.05em;
  box-shadow: 0 18px 42px rgba(79, 70, 229, 0.22);
}

.vb-fetch-19-main {
  min-width: 0;
}

.vb-fetch-19-main > span {
  display: inline-flex;
  margin-bottom: 9px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #e0e7ff;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-19-main h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(28px, 5vw, 48px) !important;
  line-height: 1.02 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-fetch-19-main p {
  margin: 0 0 18px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-19-info-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.vb-fetch-19-info-grid div {
  min-width: 0;
  padding: 14px;
  border-radius: 18px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-19-info-grid strong {
  display: block;
  margin-bottom: 4px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 13px;
  font-weight: 950;
}

.vb-fetch-19-info-grid small {
  display: block;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 13px;
  line-height: 1.45;
  font-weight: 700;
  overflow-wrap: anywhere;
}

@media (max-width: 760px) {
  .vb-fetch-19-controls,
  .vb-fetch-19-profile,
  .vb-fetch-19-info-grid {
    grid-template-columns: 1fr;
  }

  .vb-fetch-19-controls button {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-19-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-19-avatar {
    width: 72px;
    height: 72px;
  }
}

This Fetch API user profile dashboard example is useful for SaaS dashboards, CRM tools, profile pages, account areas, admin panels, client portals, and interfaces that display nested API user data.

20. Fetch API Comments Loader

A Fetch API comments loader is useful when a page needs to load related comments, reviews, replies, notes, or messages after the main content is already visible. This keeps the page lighter and loads secondary data only when needed.

This example loads comments for a selected post ID, uses query parameters, renders email and message data, and includes loading, empty, success, and error states.

Example 20

Fetch API Comments Loader

Load related comments from an API and render them in a clean discussion layout.

Select a post and load related comments.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-20-demo");
  if (!demo) return;

  const postSelect = demo.querySelector("[data-vb-fetch-20-post]");
  const button = demo.querySelector("[data-vb-fetch-20-load]");
  const status = demo.querySelector("[data-vb-fetch-20-status]");
  const list = demo.querySelector("[data-vb-fetch-20-list]");

  function setStatus(text, type) {
    status.className = "vb-fetch-20-status";

    if (type === "success") {
      status.classList.add("is-success");
    }

    if (type === "error") {
      status.classList.add("is-error");
    }

    status.textContent = text;
  }

  function initials(email) {
    return email.slice(0, 2).toUpperCase();
  }

  function renderComments(comments) {
    if (!comments.length) {
      list.innerHTML = "";
      setStatus("No comments found for this post.", "error");
      return;
    }

    list.innerHTML = comments.map(function (comment) {
      return (
        '<article class="vb-fetch-20-comment">' +
          '<div class="vb-fetch-20-avatar">' + initials(comment.email) + '</div>' +
          '<div>' +
            '<strong>' + comment.name + '</strong>' +
            '<small>' + comment.email + '</small>' +
            '<p>' + comment.body + '</p>' +
          '</div>' +
        '</article>'
      );
    }).join("");
  }

  async function loadComments() {
    const postId = postSelect.value;
    const params = new URLSearchParams({
      postId: postId
    });

    button.disabled = true;
    list.innerHTML = "";
    setStatus("Loading comments for post #" + postId + "...", "");

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/comments?" + params.toString());

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const comments = await response.json();

      renderComments(comments.slice(0, 5));
      setStatus("Loaded " + Math.min(comments.length, 5) + " comments for post #" + postId + ".", "success");
    } catch (error) {
      list.innerHTML = "";
      setStatus("Comments request failed: " + error.message, "error");
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", loadComments);
})();

HTML

<div class="vb-fetch-20-demo">
  <div class="vb-fetch-20-shell">
    <div class="vb-fetch-20-header">
      <span>Example 20</span>
      <h3>Fetch API Comments Loader</h3>
      <p>Load related comments from an API and render them in a clean discussion layout.</p>
    </div>

    <div class="vb-fetch-20-controls">
      <label>
        Post ID
        <select data-vb-fetch-20-post>
          <option value="1">Post 1</option>
          <option value="2">Post 2</option>
          <option value="3">Post 3</option>
        </select>
      </label>

      <button type="button" data-vb-fetch-20-load>Load Comments</button>
    </div>

    <div class="vb-fetch-20-status" data-vb-fetch-20-status>
      Select a post and load related comments.
    </div>

    <div class="vb-fetch-20-list" data-vb-fetch-20-list></div>
  </div>
</div>

CSS

.vb-fetch-20-demo,
.vb-fetch-20-demo * {
  box-sizing: border-box;
}

.vb-fetch-20-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(249, 115, 22, 0.15), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(168, 85, 247, 0.15), transparent 34%),
    linear-gradient(135deg, #fff7ed 0%, #faf5ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(249, 115, 22, 0.16);
  overflow: hidden;
}

.vb-fetch-20-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-20-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #7c2d12 0%, #f97316 52%, #7e22ce 100%) !important;
}

.vb-fetch-20-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #ffedd5 !important;
  -webkit-text-fill-color: #ffedd5 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-20-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-20-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #f3e8ff !important;
  -webkit-text-fill-color: #f3e8ff !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-20-controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  align-items: end;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
}

.vb-fetch-20-controls label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-20-controls select {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid #fed7aa;
  border-radius: 18px;
  background: #fff7ed;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 850;
  outline: none;
}

.vb-fetch-20-controls button {
  min-height: 52px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #f97316, #7e22ce);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(249, 115, 22, 0.22);
}

.vb-fetch-20-controls button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-20-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #fff7ed;
  border: 1px solid #fed7aa;
  color: #c2410c !important;
  -webkit-text-fill-color: #c2410c !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-20-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-20-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-20-list {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-20-comment {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 14px;
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 12% 12%, rgba(249, 115, 22, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-20-avatar {
  display: flex;
  width: 48px;
  height: 48px;
  align-items: center;
  justify-content: center;
  border-radius: 16px;
  background: linear-gradient(135deg, #f97316, #7e22ce);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  text-transform: uppercase;
}

.vb-fetch-20-comment strong {
  display: block;
  margin-bottom: 4px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 16px;
  line-height: 1.3;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-20-comment small {
  display: block;
  margin-bottom: 8px;
  color: #7e22ce !important;
  -webkit-text-fill-color: #7e22ce !important;
  font-size: 13px;
  line-height: 1.4;
  font-weight: 800;
  overflow-wrap: anywhere;
}

.vb-fetch-20-comment p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 720px) {
  .vb-fetch-20-controls,
  .vb-fetch-20-comment {
    grid-template-columns: 1fr;
  }

  .vb-fetch-20-controls button {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-20-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API comments loader example is useful for comment sections, review lists, support replies, message feeds, notes panels, discussion blocks, and any page that loads related API data after the main content.

21. Fetch API Retry Failed Request

A Fetch API retry pattern lets users try again when an API request fails. This is useful for unstable networks, temporary server errors, rate limits, loading widgets, checkout actions, admin dashboards, and API-powered forms.

This example intentionally alternates between a failed request and a successful request so the retry workflow is easy to test. It shows an error card, retry count, disabled button states, and successful recovery after retry.

Example 21

Fetch API Retry Failed Request

Show a failed API request state and let users retry until the request succeeds.

Ready

No request started

Click the button to test the retry workflow.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-21-demo");
  if (!demo) return;

  const card = demo.querySelector("[data-vb-fetch-21-card]");
  let attempts = 0;

  function fakeUnstableFetch() {
    attempts += 1;

    return new Promise(function (resolve, reject) {
      setTimeout(function () {
        if (attempts % 2 === 1) {
          reject(new Error("Temporary API error on attempt #" + attempts));
          return;
        }

        resolve({
          ok: true,
          json: function () {
            return Promise.resolve({
              id: 1,
              title: "Recovered API response",
              attempt: attempts,
              status: "success"
            });
          }
        });
      }, 800);
    });
  }

  function renderState(type, title, message, buttonText, data) {
    card.className = "vb-fetch-21-card";

    if (type) {
      card.classList.add(type);
    }

    card.innerHTML =
      '<span>' + (type ? type.replace("is-", "") : "Ready") + '</span>' +
      '<h4>' + title + '</h4>' +
      '<p>' + message + '</p>' +
      '<div class="vb-fetch-21-actions">' +
        '<button type="button" data-vb-fetch-21-run>' + buttonText + '</button>' +
      '</div>' +
      (data ? '<pre class="vb-fetch-21-json">' + JSON.stringify(data, null, 2) + '</pre>' : '');

    card.querySelector("[data-vb-fetch-21-run]").addEventListener("click", runRequest);
  }

  async function runRequest() {
    renderState("is-loading", "Request running", "Trying API request attempt #" + (attempts + 1) + "...", "Please wait");

    const activeButton = card.querySelector("[data-vb-fetch-21-run]");
    activeButton.disabled = true;

    try {
      const response = await fakeUnstableFetch();

      if (!response.ok) {
        throw new Error("HTTP request failed");
      }

      const data = await response.json();

      renderState(
        "is-success",
        "Request recovered",
        "The retry workflow succeeded on attempt #" + data.attempt + ".",
        "Run Again",
        data
      );
    } catch (error) {
      renderState(
        "is-error",
        "Request failed",
        error.message + ". Click retry to run the request again.",
        "Retry Request"
      );
    }
  }

  card.querySelector("[data-vb-fetch-21-run]").addEventListener("click", runRequest);
})();

HTML

<div class="vb-fetch-21-demo">
  <div class="vb-fetch-21-shell">
    <div class="vb-fetch-21-header">
      <span>Example 21</span>
      <h3>Fetch API Retry Failed Request</h3>
      <p>Show a failed API request state and let users retry until the request succeeds.</p>
    </div>

    <div class="vb-fetch-21-card" data-vb-fetch-21-card>
      <span>Ready</span>
      <h4>No request started</h4>
      <p>Click the button to test the retry workflow.</p>
      <div class="vb-fetch-21-actions">
        <button type="button" data-vb-fetch-21-run>Run Request</button>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-21-demo,
.vb-fetch-21-demo * {
  box-sizing: border-box;
}

.vb-fetch-21-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(239, 68, 68, 0.15), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #fef2f2 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(239, 68, 68, 0.16);
  overflow: hidden;
}

.vb-fetch-21-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-21-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #7f1d1d 0%, #ef4444 48%, #16a34a 100%) !important;
}

.vb-fetch-21-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-21-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-21-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-21-card {
  min-width: 0;
  margin: clamp(20px, 4vw, 34px);
  padding: clamp(22px, 4vw, 34px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 14% 12%, rgba(148, 163, 184, 0.12), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.08);
}

.vb-fetch-21-card.is-loading {
  background: #fffbeb;
  border-color: #fde68a;
}

.vb-fetch-21-card.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-21-card.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-21-card span {
  display: inline-flex;
  margin-bottom: 12px;
  padding: 8px 11px;
  border-radius: 999px;
  background: #e2e8f0;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-21-card.is-loading span {
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
}

.vb-fetch-21-card.is-error span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-21-card.is-success span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-21-card h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(30px, 5vw, 52px) !important;
  line-height: 1.02 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-fetch-21-card p {
  max-width: 760px;
  margin: 0 0 20px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.65;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-21-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.vb-fetch-21-actions button {
  min-height: 50px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #ef4444, #16a34a);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(239, 68, 68, 0.20);
}

.vb-fetch-21-actions button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-21-json {
  max-width: 100%;
  max-height: 220px;
  overflow: auto;
  margin: 14px 0 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #bbf7d0 !important;
  -webkit-text-fill-color: #bbf7d0 !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  .vb-fetch-21-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-21-actions button {
    width: 100%;
  }
}

This Fetch API retry failed request example is useful for resilient dashboards, checkout actions, form submissions, widgets, API cards, admin tools, and any JavaScript interface that needs a safe retry workflow after a request fails.

22. Fetch API Timeout with AbortController

A Fetch API timeout is useful when a request takes too long and the interface should not wait forever. JavaScript can use AbortController together with setTimeout() to cancel slow requests and show a clear timeout message.

This example simulates a slow API request, starts a timeout timer, cancels the request if it takes too long, and gives users a controlled error state instead of a frozen loading screen.

Example 22

Fetch API Timeout with AbortController

Cancel a slow API request after a timeout and show a safe UI error state.

Ready

No request running

Run a fast request or a slow request to test timeout handling with AbortController.

{}

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-22-demo");
  if (!demo) return;

  const panel = demo.querySelector("[data-vb-fetch-22-panel]");
  const output = demo.querySelector("[data-vb-fetch-22-output]");
  const fastButton = demo.querySelector("[data-vb-fetch-22-fast]");
  const slowButton = demo.querySelector("[data-vb-fetch-22-slow]");

  function fakeFetchWithAbort(delay, signal) {
    return new Promise(function (resolve, reject) {
      const timer = setTimeout(function () {
        resolve({
          ok: true,
          json: function () {
            return Promise.resolve({
              id: 22,
              delay: delay,
              message: "Request completed before timeout."
            });
          }
        });
      }, delay);

      signal.addEventListener("abort", function () {
        clearTimeout(timer);
        reject(new DOMException("Request timeout reached", "AbortError"));
      });
    });
  }

  function setPanel(type, label, title, message, data) {
    panel.className = "vb-fetch-22-panel";

    if (type) {
      panel.classList.add(type);
    }

    panel.querySelector("span").textContent = label;
    panel.querySelector("h4").textContent = title;
    panel.querySelector("p").textContent = message;
    output.textContent = JSON.stringify(data || {}, null, 2);
  }

  function setButtons(disabled) {
    fastButton.disabled = disabled;
    slowButton.disabled = disabled;
  }

  async function runRequest(delay) {
    const controller = new AbortController();
    const timeoutMs = 1200;

    setButtons(true);
    setPanel("is-loading", "Loading", "Request running", "Timeout is set to " + timeoutMs + "ms.", {
      delay: delay,
      timeout: timeoutMs
    });

    const timeoutId = setTimeout(function () {
      controller.abort();
    }, timeoutMs);

    try {
      const response = await fakeFetchWithAbort(delay, controller.signal);

      if (!response.ok) {
        throw new Error("HTTP request failed");
      }

      const data = await response.json();

      clearTimeout(timeoutId);
      setPanel("is-success", "Success", "Request completed", "The API-style request completed before timeout.", data);
    } catch (error) {
      clearTimeout(timeoutId);

      if (error.name === "AbortError") {
        setPanel("is-error", "Timeout", "Request cancelled", "The request took too long and was aborted safely.", {
          error: error.message,
          timeout: timeoutMs
        });
      } else {
        setPanel("is-error", "Error", "Request failed", error.message, {
          error: error.message
        });
      }
    } finally {
      setButtons(false);
    }
  }

  fastButton.addEventListener("click", function () {
    runRequest(600);
  });

  slowButton.addEventListener("click", function () {
    runRequest(2200);
  });
})();

HTML

<div class="vb-fetch-22-demo">
  <div class="vb-fetch-22-shell">
    <div class="vb-fetch-22-header">
      <span>Example 22</span>
      <h3>Fetch API Timeout with AbortController</h3>
      <p>Cancel a slow API request after a timeout and show a safe UI error state.</p>
    </div>

    <div class="vb-fetch-22-panel" data-vb-fetch-22-panel>
      <span>Ready</span>
      <h4>No request running</h4>
      <p>Run a fast request or a slow request to test timeout handling with AbortController.</p>

      <div class="vb-fetch-22-actions">
        <button type="button" data-vb-fetch-22-fast>Run Fast Request</button>
        <button type="button" data-vb-fetch-22-slow>Run Slow Timeout</button>
      </div>

      <pre data-vb-fetch-22-output>{}</pre>
    </div>
  </div>
</div>

CSS

.vb-fetch-22-demo,
.vb-fetch-22-demo * {
  box-sizing: border-box;
}

.vb-fetch-22-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(37, 99, 235, 0.17), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(239, 68, 68, 0.15), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #fef2f2 54%, #ffffff 100%) !important;
  border: 1px solid rgba(37, 99, 235, 0.16);
  overflow: hidden;
}

.vb-fetch-22-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-22-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #1e3a8a 0%, #2563eb 48%, #dc2626 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-22-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-22-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-22-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-22-panel {
  min-width: 0;
  margin: clamp(20px, 4vw, 34px);
  padding: clamp(22px, 4vw, 34px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 14% 12%, rgba(37, 99, 235, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.08);
}

.vb-fetch-22-panel.is-loading {
  background: #fffbeb;
  border-color: #fde68a;
}

.vb-fetch-22-panel.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-22-panel.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-22-panel span {
  display: inline-flex;
  margin-bottom: 12px;
  padding: 8px 11px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-22-panel.is-loading span {
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
}

.vb-fetch-22-panel.is-success span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-22-panel.is-error span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-22-panel h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(30px, 5vw, 52px) !important;
  line-height: 1.02 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-fetch-22-panel p {
  max-width: 780px;
  margin: 0 0 20px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.65;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-22-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 16px;
}

.vb-fetch-22-actions button {
  min-height: 50px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
}

.vb-fetch-22-actions button:first-child {
  background: linear-gradient(135deg, #2563eb, #16a34a);
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.20);
}

.vb-fetch-22-actions button:last-child {
  background: linear-gradient(135deg, #dc2626, #f97316);
  box-shadow: 0 16px 38px rgba(220, 38, 38, 0.20);
}

.vb-fetch-22-actions button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-22-panel pre {
  max-width: 100%;
  max-height: 230px;
  overflow: auto;
  margin: 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  .vb-fetch-22-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-22-actions button {
    width: 100%;
  }
}

This Fetch API timeout example is useful for slow API widgets, admin dashboards, checkout steps, forms, search interfaces, external integrations, and any JavaScript request that needs safe cancellation.

23. Fetch API Multiple Requests with Promise.all

Fetch API multiple requests are useful when a page needs several pieces of data at the same time. Instead of waiting for one request and then starting the next, JavaScript can run them in parallel with Promise.all().

This example loads a user profile, posts, and todos at the same time. When all requests finish, it renders a combined dashboard summary from multiple API endpoints.

Example 23

Multiple Fetch Requests with Promise.all

Load several API endpoints in parallel and combine the results into one dashboard.

Load multiple endpoints to build the dashboard.
User --
Posts --
Todos --

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-23-demo");
  if (!demo) return;

  const userSelect = demo.querySelector("[data-vb-fetch-23-user]");
  const button = demo.querySelector("[data-vb-fetch-23-load]");
  const status = demo.querySelector("[data-vb-fetch-23-status]");
  const summary = demo.querySelector("[data-vb-fetch-23-summary]");
  const output = demo.querySelector("[data-vb-fetch-23-output]");

  function setStatus(text, type) {
    status.className = "vb-fetch-23-status";

    if (type === "success") {
      status.classList.add("is-success");
    }

    if (type === "error") {
      status.classList.add("is-error");
    }

    status.textContent = text;
  }

  function renderDashboard(user, posts, todos) {
    const completedTodos = todos.filter(function (todo) {
      return todo.completed;
    }).length;

    summary.innerHTML =
      '<div><span>User</span><strong>' + user.name + '</strong></div>' +
      '<div><span>Posts</span><strong>' + posts.length + '</strong></div>' +
      '<div><span>Done Todos</span><strong>' + completedTodos + '/' + todos.length + '</strong></div>';

    output.innerHTML =
      '<article class="vb-fetch-23-card">' +
        '<span>Profile endpoint</span>' +
        '<h4>' + user.company.name + '</h4>' +
        '<p>' + user.email + ' · ' + user.website + '</p>' +
      '</article>' +
      posts.slice(0, 3).map(function (post) {
        return (
          '<article class="vb-fetch-23-card">' +
            '<span>Post #' + post.id + '</span>' +
            '<h4>' + post.title + '</h4>' +
            '<p>' + post.body + '</p>' +
          '</article>'
        );
      }).join("");
  }

  async function loadDashboard() {
    const userId = userSelect.value;

    button.disabled = true;
    output.innerHTML = "";
    setStatus("Loading user, posts, and todos in parallel...", "");

    try {
      const urls = [
        "https://jsonplaceholder.typicode.com/users/" + userId,
        "https://jsonplaceholder.typicode.com/posts?userId=" + userId,
        "https://jsonplaceholder.typicode.com/todos?userId=" + userId
      ];

      const responses = await Promise.all(urls.map(function (url) {
        return fetch(url);
      }));

      responses.forEach(function (response) {
        if (!response.ok) {
          throw new Error("One request failed with HTTP status " + response.status);
        }
      });

      const data = await Promise.all(responses.map(function (response) {
        return response.json();
      }));

      renderDashboard(data[0], data[1], data[2]);
      setStatus("All 3 API requests finished successfully with Promise.all().", "success");
    } catch (error) {
      setStatus("Multiple request workflow failed: " + error.message, "error");
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", loadDashboard);
})();

HTML

<div class="vb-fetch-23-demo">
  <div class="vb-fetch-23-shell">
    <div class="vb-fetch-23-header">
      <span>Example 23</span>
      <h3>Multiple Fetch Requests with Promise.all</h3>
      <p>Load several API endpoints in parallel and combine the results into one dashboard.</p>
    </div>

    <div class="vb-fetch-23-controls">
      <label>
        Dashboard user
        <select data-vb-fetch-23-user>
          <option value="1">User 1</option>
          <option value="2">User 2</option>
          <option value="3">User 3</option>
        </select>
      </label>

      <button type="button" data-vb-fetch-23-load>Load Dashboard</button>
    </div>

    <div class="vb-fetch-23-status" data-vb-fetch-23-status>
      Load multiple endpoints to build the dashboard.
    </div>

    <div class="vb-fetch-23-summary" data-vb-fetch-23-summary>
      <div>
        <span>User</span>
        <strong>--</strong>
      </div>
      <div>
        <span>Posts</span>
        <strong>--</strong>
      </div>
      <div>
        <span>Todos</span>
        <strong>--</strong>
      </div>
    </div>

    <div class="vb-fetch-23-output" data-vb-fetch-23-output></div>
  </div>
</div>

CSS

.vb-fetch-23-demo,
.vb-fetch-23-demo * {
  box-sizing: border-box;
}

.vb-fetch-23-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(124, 58, 237, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #f5f3ff 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(124, 58, 237, 0.16);
  overflow: hidden;
}

.vb-fetch-23-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-23-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #4c1d95 0%, #7c3aed 52%, #16a34a 100%) !important;
}

.vb-fetch-23-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #ede9fe !important;
  -webkit-text-fill-color: #ede9fe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-23-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-23-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-23-controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  align-items: end;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
}

.vb-fetch-23-controls label {
  display: grid;
  gap: 8px;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-23-controls select {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid #ddd6fe;
  border-radius: 18px;
  background: #faf5ff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 850;
  outline: none;
}

.vb-fetch-23-controls button {
  min-height: 52px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #7c3aed, #16a34a);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(124, 58, 237, 0.22);
}

.vb-fetch-23-controls button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-23-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #f5f3ff;
  border: 1px solid #ddd6fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-23-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-23-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-23-summary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
}

.vb-fetch-23-summary div {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(124, 58, 237, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-23-summary span {
  display: block;
  margin-bottom: 8px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-fetch-23-summary strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(28px, 4vw, 44px);
  line-height: 1;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-23-output {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-23-card {
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-23-card span {
  display: inline-flex;
  margin-bottom: 9px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #ede9fe;
  color: #6d28d9 !important;
  -webkit-text-fill-color: #6d28d9 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-23-card h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-23-card p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 800px) {
  .vb-fetch-23-controls,
  .vb-fetch-23-summary,
  .vb-fetch-23-output {
    grid-template-columns: 1fr;
  }

  .vb-fetch-23-controls button {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-23-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API multiple requests example is useful for dashboards, SaaS home screens, analytics panels, account pages, CRM overviews, admin tools, and any interface that needs several API resources at once.

24. Fetch API Dependent Requests

Fetch API dependent requests are used when one API request depends on data from another request. For example, JavaScript may first load a user, then use that user ID to load posts, orders, invoices, comments, or related records.

This example first loads a user profile, then automatically loads that user’s posts. The second request depends on the result of the first request, which makes this a useful pattern for real connected data workflows.

Example 24

Fetch API Dependent Requests

Load one API resource first, then use its data to request related records.

Step 1 Waiting for user request

The first request loads user data.

Step 2 Waiting for posts request

The second request uses the user ID.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-24-demo");
  if (!demo) return;

  const userSelect = demo.querySelector("[data-vb-fetch-24-user]");
  const button = demo.querySelector("[data-vb-fetch-24-load]");
  const flow = demo.querySelector("[data-vb-fetch-24-flow]");
  const results = demo.querySelector("[data-vb-fetch-24-results]");

  function renderStep(index, state, title, message) {
    const step = flow.children[index];

    step.className = "vb-fetch-24-step";

    if (state) {
      step.classList.add(state);
    }

    step.querySelector("strong").textContent = title;
    step.querySelector("p").textContent = message;
  }

  function renderResults(user, posts) {
    results.innerHTML =
      '<article class="vb-fetch-24-user-card">' +
        '<span>First request result</span>' +
        '<h4>' + user.name + '</h4>' +
        '<p>' + user.email + ' · ' + user.company.name + '</p>' +
      '</article>' +
      posts.slice(0, 4).map(function (post) {
        return (
          '<article class="vb-fetch-24-post-card">' +
            '<span>Related post #' + post.id + '</span>' +
            '<h4>' + post.title + '</h4>' +
            '<p>' + post.body + '</p>' +
          '</article>'
        );
      }).join("");
  }

  async function loadDependentData() {
    const userId = userSelect.value;

    button.disabled = true;
    results.innerHTML = "";
    renderStep(0, "is-loading", "Loading user", "Requesting user #" + userId + " first.");
    renderStep(1, "", "Waiting for user data", "Posts request starts after user data is loaded.");

    try {
      const userResponse = await fetch("https://jsonplaceholder.typicode.com/users/" + userId);

      if (!userResponse.ok) {
        throw new Error("User request failed with HTTP status " + userResponse.status);
      }

      const user = await userResponse.json();

      renderStep(0, "is-success", "User loaded", "User ID " + user.id + " was loaded successfully.");
      renderStep(1, "is-loading", "Loading related posts", "Using user ID " + user.id + " for the second request.");

      const postsResponse = await fetch("https://jsonplaceholder.typicode.com/posts?userId=" + user.id);

      if (!postsResponse.ok) {
        throw new Error("Posts request failed with HTTP status " + postsResponse.status);
      }

      const posts = await postsResponse.json();

      renderStep(1, "is-success", "Related posts loaded", "Loaded " + posts.length + " posts for user ID " + user.id + ".");
      renderResults(user, posts);
    } catch (error) {
      renderStep(0, "is-error", "Workflow failed", error.message);
      renderStep(1, "is-error", "Stopped", "The second request did not run or did not complete.");
      results.innerHTML = "";
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", loadDependentData);
})();

HTML

<div class="vb-fetch-24-demo">
  <div class="vb-fetch-24-shell">
    <div class="vb-fetch-24-header">
      <span>Example 24</span>
      <h3>Fetch API Dependent Requests</h3>
      <p>Load one API resource first, then use its data to request related records.</p>
    </div>

    <div class="vb-fetch-24-controls">
      <label>
        Start with user ID
        <select data-vb-fetch-24-user>
          <option value="1">User 1</option>
          <option value="2">User 2</option>
          <option value="3">User 3</option>
        </select>
      </label>

      <button type="button" data-vb-fetch-24-load>Load User + Posts</button>
    </div>

    <div class="vb-fetch-24-flow" data-vb-fetch-24-flow>
      <div class="vb-fetch-24-step">
        <span>Step 1</span>
        <strong>Waiting for user request</strong>
        <p>The first request loads user data.</p>
      </div>
      <div class="vb-fetch-24-step">
        <span>Step 2</span>
        <strong>Waiting for posts request</strong>
        <p>The second request uses the user ID.</p>
      </div>
    </div>

    <div class="vb-fetch-24-results" data-vb-fetch-24-results></div>
  </div>
</div>

CSS

.vb-fetch-24-demo,
.vb-fetch-24-demo * {
  box-sizing: border-box;
}

.vb-fetch-24-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(20, 184, 166, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(245, 158, 11, 0.15), transparent 34%),
    linear-gradient(135deg, #f0fdfa 0%, #fffbeb 54%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.16);
  overflow: hidden;
}

.vb-fetch-24-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-24-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #115e59 0%, #14b8a6 52%, #f59e0b 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-24-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-24-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(34px, 5vw, 64px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-24-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #fef3c7 !important;
  -webkit-text-fill-color: #fef3c7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-24-controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 14px;
  align-items: end;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
}

.vb-fetch-24-controls label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-24-controls select {
  width: 100%;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid #99f6e4;
  border-radius: 18px;
  background: #f0fdfa;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 850;
  outline: none;
}

.vb-fetch-24-controls button {
  min-height: 52px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #14b8a6, #f59e0b);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(20, 184, 166, 0.22);
}

.vb-fetch-24-controls button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-24-flow {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
}

.vb-fetch-24-step {
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background: #f8fafc;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-24-step.is-loading {
  background: #fffbeb;
  border-color: #fde68a;
}

.vb-fetch-24-step.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-24-step.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-24-step span {
  display: inline-flex;
  margin-bottom: 9px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-24-step strong {
  display: block;
  margin-bottom: 7px;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px;
  line-height: 1.25;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-24-step p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-24-results {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-24-user-card,
.vb-fetch-24-post-card {
  min-width: 0;
  padding: 18px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(20, 184, 166, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-24-user-card span,
.vb-fetch-24-post-card span {
  display: inline-flex;
  margin-bottom: 9px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #fef3c7;
  color: #92400e !important;
  -webkit-text-fill-color: #92400e !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-24-user-card h4,
.vb-fetch-24-post-card h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 20px !important;
  line-height: 1.22 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-24-user-card p,
.vb-fetch-24-post-card p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 800px) {
  .vb-fetch-24-controls,
  .vb-fetch-24-flow {
    grid-template-columns: 1fr;
  }

  .vb-fetch-24-controls button {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-24-header h3 {
    font-size: 38px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API dependent requests example is useful for user portals, ecommerce account pages, CRM records, order details, invoice lists, project dashboards, and any JavaScript workflow where one API request depends on another.

25. Fetch API Form Submission with Validation

A Fetch API form submission is one of the most common JavaScript API use cases. It lets a form validate user input, send JSON data to an API, show loading feedback, handle errors, and display a success message without reloading the page.

This example validates a contact-style form, prevents empty fields, sends the data as a JSON POST request, disables the button while loading, and renders the API response after the request succeeds.

Example 25

Fetch API Form Submission with Validation

Validate form fields, submit JSON with Fetch API, and show a clean success response.

Waiting

No form submitted yet

Fill the form and send a JSON POST request.

{}

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-25-demo");
  if (!demo) return;

  const form = demo.querySelector("[data-vb-fetch-25-form]");
  const nameInput = demo.querySelector("[data-vb-fetch-25-name]");
  const emailInput = demo.querySelector("[data-vb-fetch-25-email]");
  const messageInput = demo.querySelector("[data-vb-fetch-25-message]");
  const submitButton = demo.querySelector("[data-vb-fetch-25-submit]");
  const responseBox = demo.querySelector("[data-vb-fetch-25-response]");

  function setError(field, message) {
    const error = demo.querySelector('[data-vb-fetch-25-error="' + field + '"]');
    if (error) {
      error.textContent = message;
    }
  }

  function clearErrors() {
    setError("name", "");
    setError("email", "");
    setError("message", "");
  }

  function validateForm(payload) {
    let valid = true;
    clearErrors();

    if (payload.name.length < 2) {
      setError("name", "Name must be at least 2 characters.");
      valid = false;
    }

    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(payload.email)) {
      setError("email", "Enter a valid email address.");
      valid = false;
    }

    if (payload.message.length < 10) {
      setError("message", "Message must be at least 10 characters.");
      valid = false;
    }

    return valid;
  }

  function renderResponse(type, title, message, data) {
    responseBox.className = "vb-fetch-25-response";

    if (type) {
      responseBox.classList.add(type);
    }

    responseBox.innerHTML =
      '<span>' + (type === "is-success" ? "Success" : type === "is-error" ? "Error" : "Status") + '</span>' +
      '<h4>' + title + '</h4>' +
      '<p>' + message + '</p>' +
      '<pre>' + JSON.stringify(data || {}, null, 2) + '</pre>';
  }

  form.addEventListener("submit", async function (event) {
    event.preventDefault();

    const payload = {
      title: nameInput.value.trim(),
      body: messageInput.value.trim(),
      email: emailInput.value.trim(),
      userId: 1
    };

    const validationPayload = {
      name: payload.title,
      email: payload.email,
      message: payload.body
    };

    if (!validateForm(validationPayload)) {
      renderResponse("is-error", "Validation failed", "Fix the form errors before sending the Fetch API request.", validationPayload);
      return;
    }

    submitButton.disabled = true;
    submitButton.textContent = "Submitting...";
    renderResponse("", "Sending request", "Posting JSON form data to the API...", payload);

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const data = await response.json();

      renderResponse("is-success", "Form submitted", "The API returned a successful JSON response.", data);
      form.reset();
      clearErrors();
    } catch (error) {
      renderResponse("is-error", "Submission failed", error.message, payload);
    } finally {
      submitButton.disabled = false;
      submitButton.textContent = "Submit with Fetch";
    }
  });
})();

HTML

<div class="vb-fetch-25-demo">
  <div class="vb-fetch-25-shell">
    <div class="vb-fetch-25-header">
      <span>Example 25</span>
      <h3>Fetch API Form Submission with Validation</h3>
      <p>Validate form fields, submit JSON with Fetch API, and show a clean success response.</p>
    </div>

    <div class="vb-fetch-25-body">
      <form class="vb-fetch-25-form" data-vb-fetch-25-form>
        <label>
          Full name
          <input type="text" data-vb-fetch-25-name placeholder="Jane Developer">
          <small data-vb-fetch-25-error="name"></small>
        </label>

        <label>
          Email address
          <input type="email" data-vb-fetch-25-email placeholder="jane@example.com">
          <small data-vb-fetch-25-error="email"></small>
        </label>

        <label>
          Message
          <textarea data-vb-fetch-25-message placeholder="Tell us what you need..."></textarea>
          <small data-vb-fetch-25-error="message"></small>
        </label>

        <button type="submit" data-vb-fetch-25-submit>Submit with Fetch</button>
      </form>

      <div class="vb-fetch-25-response" data-vb-fetch-25-response>
        <span>Waiting</span>
        <h4>No form submitted yet</h4>
        <p>Fill the form and send a JSON POST request.</p>
        <pre>{}</pre>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-25-demo,
.vb-fetch-25-demo * {
  box-sizing: border-box;
}

.vb-fetch-25-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(59, 130, 246, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(168, 85, 247, 0.16), transparent 34%),
    linear-gradient(135deg, #eff6ff 0%, #faf5ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(59, 130, 246, 0.16);
  overflow: hidden;
}

.vb-fetch-25-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-25-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #1e3a8a 0%, #2563eb 50%, #9333ea 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-25-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-25-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-25-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #f3e8ff !important;
  -webkit-text-fill-color: #f3e8ff !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-25-body {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
  gap: 22px;
  padding: clamp(20px, 4vw, 34px);
  min-width: 0;
}

.vb-fetch-25-form {
  display: grid;
  gap: 14px;
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background: #eff6ff;
  border: 1px solid #bfdbfe;
}

.vb-fetch-25-form label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-25-form input,
.vb-fetch-25-form textarea {
  width: 100%;
  min-width: 0;
  border: 1px solid #bfdbfe;
  border-radius: 16px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 750;
  outline: none;
}

.vb-fetch-25-form input {
  min-height: 50px;
  padding: 0 14px;
}

.vb-fetch-25-form textarea {
  min-height: 130px;
  padding: 14px;
  resize: vertical;
}

.vb-fetch-25-form small {
  min-height: 17px;
  color: #dc2626 !important;
  -webkit-text-fill-color: #dc2626 !important;
  font-size: 12px;
  line-height: 1.35;
  font-weight: 850;
}

.vb-fetch-25-form button {
  min-height: 52px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #2563eb, #9333ea);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.24);
}

.vb-fetch-25-form button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-25-response {
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background:
    radial-gradient(circle at 14% 12%, rgba(147, 51, 234, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-25-response.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-25-response.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-25-response span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #e0e7ff;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-25-response.is-success span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-25-response.is-error span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-25-response h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(26px, 4vw, 42px) !important;
  line-height: 1.05 !important;
  font-weight: 950 !important;
  letter-spacing: -0.05em;
  overflow-wrap: anywhere;
}

.vb-fetch-25-response p {
  margin: 0 0 14px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-25-response pre {
  max-width: 100%;
  max-height: 240px;
  overflow: auto;
  margin: 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-fetch-25-body {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-25-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API form submission example is useful for contact forms, quote request forms, signup forms, lead forms, support forms, feedback forms, and any JavaScript form that sends JSON data to an API.

26. Fetch API File Upload Progress UI

A file upload UI is a common API feature for dashboards, profile pages, document portals, support tickets, admin panels, and SaaS tools. Native fetch() does not provide upload progress events in all browsers the same way XMLHttpRequest does, so this demo uses a Fetch-style async upload simulation to show the complete UI workflow safely without sending files to a real server.

This example validates the selected file, shows file name and size, animates a progress bar, handles success and error states, and keeps the demo safe because it does not upload anything to your server.

Example 26

Fetch API File Upload Progress UI

Select a file, validate it, and simulate a safe API upload progress workflow.

Waiting

No file selected

Choose a file to prepare the upload UI.

{}

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-26-demo");
  if (!demo) return;

  const fileInput = demo.querySelector("[data-vb-fetch-26-file]");
  const uploadButton = demo.querySelector("[data-vb-fetch-26-upload]");
  const statusBox = demo.querySelector("[data-vb-fetch-26-status]");
  const progressBar = demo.querySelector("[data-vb-fetch-26-bar]");
  const output = demo.querySelector("[data-vb-fetch-26-output]");

  let selectedFile = null;
  let uploading = false;

  function formatSize(bytes) {
    if (bytes < 1024) return bytes + " B";
    if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + " KB";
    return (bytes / 1024 / 1024).toFixed(2) + " MB";
  }

  function setStatus(type, label, title, message, data, progress) {
    statusBox.className = "vb-fetch-26-status";

    if (type) {
      statusBox.classList.add(type);
    }

    statusBox.querySelector("span").textContent = label;
    statusBox.querySelector("h4").textContent = title;
    statusBox.querySelector("p").textContent = message;
    output.textContent = JSON.stringify(data || {}, null, 2);
    progressBar.style.width = (progress || 0) + "%";
  }

  function validateFile(file) {
    if (!file) {
      return "Choose a file first.";
    }

    if (file.size > 2 * 1024 * 1024) {
      return "File is too large. Maximum size is 2 MB.";
    }

    return "";
  }

  fileInput.addEventListener("change", function () {
    selectedFile = fileInput.files[0] || null;
    const error = validateFile(selectedFile);

    if (error) {
      setStatus("is-error", "Invalid", "File not accepted", error, {}, 0);
      return;
    }

    setStatus(
      "",
      "Selected",
      selectedFile.name,
      "Ready to simulate upload. File size: " + formatSize(selectedFile.size) + ".",
      {
        name: selectedFile.name,
        type: selectedFile.type || "unknown",
        size: formatSize(selectedFile.size)
      },
      0
    );
  });

  uploadButton.addEventListener("click", function () {
    if (uploading) return;

    const error = validateFile(selectedFile);

    if (error) {
      setStatus("is-error", "Error", "Upload blocked", error, {}, 0);
      return;
    }

    uploading = true;
    uploadButton.disabled = true;
    uploadButton.textContent = "Uploading...";

    let progress = 0;

    setStatus(
      "",
      "Uploading",
      selectedFile.name,
      "Simulating Fetch-style file upload progress...",
      {
        name: selectedFile.name,
        size: formatSize(selectedFile.size),
        note: "Demo only. No file is sent to a server."
      },
      progress
    );

    const timer = setInterval(function () {
      progress += Math.floor(Math.random() * 16) + 8;

      if (progress >= 100) {
        progress = 100;
        clearInterval(timer);

        setStatus(
          "is-success",
          "Complete",
          "Upload UI completed",
          "The file upload workflow finished successfully in the demo UI.",
          {
            file: selectedFile.name,
            uploaded: true,
            progress: "100%",
            server: "No real server upload in this safe demo"
          },
          progress
        );

        uploading = false;
        uploadButton.disabled = false;
        uploadButton.textContent = "Start Upload";
        return;
      }

      setStatus(
        "",
        "Uploading",
        selectedFile.name,
        "Upload progress: " + progress + "%",
        {
          file: selectedFile.name,
          progress: progress + "%"
        },
        progress
      );
    }, 280);
  });
})();

HTML

<div class="vb-fetch-26-demo">
  <div class="vb-fetch-26-shell">
    <div class="vb-fetch-26-header">
      <span>Example 26</span>
      <h3>Fetch API File Upload Progress UI</h3>
      <p>Select a file, validate it, and simulate a safe API upload progress workflow.</p>
    </div>

    <div class="vb-fetch-26-body">
      <div class="vb-fetch-26-uploader">
        <label class="vb-fetch-26-drop">
          <input type="file" data-vb-fetch-26-file>
          <strong>Choose a file</strong>
          <span>Max 2 MB. Demo does not upload to a server.</span>
        </label>

        <button type="button" data-vb-fetch-26-upload>Start Upload</button>
      </div>

      <div class="vb-fetch-26-status" data-vb-fetch-26-status>
        <span>Waiting</span>
        <h4>No file selected</h4>
        <p>Choose a file to prepare the upload UI.</p>

        <div class="vb-fetch-26-progress">
          <div data-vb-fetch-26-bar></div>
        </div>

        <pre data-vb-fetch-26-output>{}</pre>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-26-demo,
.vb-fetch-26-demo * {
  box-sizing: border-box;
}

.vb-fetch-26-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(20, 184, 166, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdfa 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(20, 184, 166, 0.16);
  overflow: hidden;
}

.vb-fetch-26-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-26-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.16), transparent 34%),
    linear-gradient(135deg, #115e59 0%, #14b8a6 52%, #2563eb 100%) !important;
}

.vb-fetch-26-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-26-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-26-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-26-body {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: 22px;
  padding: clamp(20px, 4vw, 34px);
  min-width: 0;
}

.vb-fetch-26-uploader {
  display: grid;
  align-content: start;
  gap: 14px;
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background: #f0fdfa;
  border: 1px solid #99f6e4;
}

.vb-fetch-26-drop {
  display: grid;
  place-items: center;
  min-height: 220px;
  gap: 10px;
  padding: 24px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 18% 16%, rgba(20, 184, 166, 0.12), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 2px dashed #5eead4;
  text-align: center;
  cursor: pointer;
}

.vb-fetch-26-drop input {
  width: 1px;
  height: 1px;
  opacity: 0;
  position: absolute;
  pointer-events: none;
}

.vb-fetch-26-drop strong {
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 24px;
  line-height: 1.1;
  font-weight: 950;
}

.vb-fetch-26-drop span {
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 750;
}

.vb-fetch-26-uploader button {
  min-height: 52px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #14b8a6, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(20, 184, 166, 0.22);
}

.vb-fetch-26-uploader button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-26-status {
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background:
    radial-gradient(circle at 14% 12%, rgba(37, 99, 235, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-26-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-26-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-26-status span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #ccfbf1;
  color: #0f766e !important;
  -webkit-text-fill-color: #0f766e !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-26-status.is-success span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-26-status.is-error span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-26-status h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(28px, 5vw, 48px) !important;
  line-height: 1.02 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-fetch-26-status p {
  margin: 0 0 16px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-26-progress {
  width: 100%;
  height: 16px;
  overflow: hidden;
  margin: 0 0 16px;
  border-radius: 999px;
  background: #e2e8f0;
}

.vb-fetch-26-progress div {
  width: 0%;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(135deg, #14b8a6, #2563eb);
  transition: width 0.25s ease;
}

.vb-fetch-26-status pre {
  max-width: 100%;
  max-height: 220px;
  overflow: auto;
  margin: 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #ccfbf1 !important;
  -webkit-text-fill-color: #ccfbf1 !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 860px) {
  .vb-fetch-26-body {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-26-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.045em;
  }
}

This file upload progress UI example is useful for upload forms, profile image uploaders, document portals, support ticket attachments, admin dashboards, client portals, and SaaS file management interfaces.

27. Fetch API Cache with LocalStorage

Fetch API caching with localStorage helps reduce repeated API requests and makes small interfaces feel faster. JavaScript can save API data locally, reuse it for a limited time, and refresh it when the cache expires or when the user clicks a refresh button.

This example loads posts from an API, stores the response in localStorage with a timestamp, shows whether data came from cache or network, and lets users clear or refresh the cache manually.

Example 27

Fetch API Cache with LocalStorage

Cache API results locally, reuse fresh data, and refresh the cache when needed.

Cache is ready. Click load to check localStorage first.

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-27-demo");
  if (!demo) return;

  const loadButton = demo.querySelector("[data-vb-fetch-27-load]");
  const refreshButton = demo.querySelector("[data-vb-fetch-27-refresh]");
  const clearButton = demo.querySelector("[data-vb-fetch-27-clear]");
  const status = demo.querySelector("[data-vb-fetch-27-status]");
  const grid = demo.querySelector("[data-vb-fetch-27-grid]");

  const cacheKey = "vb_fetch_27_posts_cache";
  const cacheTimeKey = "vb_fetch_27_posts_cache_time";
  const maxAge = 60 * 1000;

  function setStatus(text, type) {
    status.className = "vb-fetch-27-status";

    if (type) {
      status.classList.add(type);
    }

    status.textContent = text;
  }

  function setButtons(disabled) {
    loadButton.disabled = disabled;
    refreshButton.disabled = disabled;
    clearButton.disabled = disabled;
  }

  function renderPosts(posts) {
    grid.innerHTML = posts.slice(0, 6).map(function (post) {
      return (
        '<article class="vb-fetch-27-card">' +
          '<span>Post #' + post.id + '</span>' +
          '<h4>' + post.title + '</h4>' +
          '<p>' + post.body + '</p>' +
        '</article>'
      );
    }).join("");
  }

  function getCachedPosts() {
    const cached = localStorage.getItem(cacheKey);
    const cachedTime = Number(localStorage.getItem(cacheTimeKey) || 0);
    const fresh = cached && Date.now() - cachedTime < maxAge;

    if (!fresh) {
      return null;
    }

    return {
      posts: JSON.parse(cached),
      age: Math.round((Date.now() - cachedTime) / 1000)
    };
  }

  async function fetchPostsFromNetwork() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=6");

    if (!response.ok) {
      throw new Error("HTTP status " + response.status);
    }

    const posts = await response.json();

    localStorage.setItem(cacheKey, JSON.stringify(posts));
    localStorage.setItem(cacheTimeKey, String(Date.now()));

    return posts;
  }

  async function loadPosts(forceRefresh) {
    setButtons(true);
    grid.innerHTML = "";

    try {
      if (!forceRefresh) {
        const cached = getCachedPosts();

        if (cached) {
          renderPosts(cached.posts);
          setStatus("Loaded from localStorage cache. Cache age: " + cached.age + " seconds.", "");
          setButtons(false);
          return;
        }
      }

      setStatus("Fetching fresh data from network and updating cache...", "is-network");
      const posts = await fetchPostsFromNetwork();
      renderPosts(posts);
      setStatus("Loaded fresh API data and saved it to localStorage cache.", "is-network");
    } catch (error) {
      setStatus("Cache workflow failed: " + error.message, "is-error");
    } finally {
      setButtons(false);
    }
  }

  loadButton.addEventListener("click", function () {
    loadPosts(false);
  });

  refreshButton.addEventListener("click", function () {
    loadPosts(true);
  });

  clearButton.addEventListener("click", function () {
    localStorage.removeItem(cacheKey);
    localStorage.removeItem(cacheTimeKey);
    grid.innerHTML = "";
    setStatus("Cache cleared. Next load will fetch fresh API data.", "is-network");
  });
})();

HTML

<div class="vb-fetch-27-demo">
  <div class="vb-fetch-27-shell">
    <div class="vb-fetch-27-header">
      <span>Example 27</span>
      <h3>Fetch API Cache with LocalStorage</h3>
      <p>Cache API results locally, reuse fresh data, and refresh the cache when needed.</p>
    </div>

    <div class="vb-fetch-27-toolbar">
      <button type="button" data-vb-fetch-27-load>Load Cached Data</button>
      <button type="button" data-vb-fetch-27-refresh>Force Refresh</button>
      <button type="button" data-vb-fetch-27-clear>Clear Cache</button>
    </div>

    <div class="vb-fetch-27-status" data-vb-fetch-27-status>
      Cache is ready. Click load to check localStorage first.
    </div>

    <div class="vb-fetch-27-grid" data-vb-fetch-27-grid></div>
  </div>
</div>

CSS

.vb-fetch-27-demo,
.vb-fetch-27-demo * {
  box-sizing: border-box;
}

.vb-fetch-27-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(34, 197, 94, 0.16), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(14, 165, 233, 0.16), transparent 34%),
    linear-gradient(135deg, #f0fdf4 0%, #f0f9ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(34, 197, 94, 0.16);
  overflow: hidden;
}

.vb-fetch-27-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-27-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #14532d 0%, #16a34a 52%, #0284c7 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-27-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-27-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-27-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #e0f2fe !important;
  -webkit-text-fill-color: #e0f2fe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-27-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
}

.vb-fetch-27-toolbar button {
  min-height: 50px;
  padding: 0 18px;
  border: 0;
  border-radius: 999px;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
}

.vb-fetch-27-toolbar button:nth-child(1) {
  background: linear-gradient(135deg, #16a34a, #0284c7);
  box-shadow: 0 16px 38px rgba(34, 197, 94, 0.20);
}

.vb-fetch-27-toolbar button:nth-child(2) {
  background: linear-gradient(135deg, #2563eb, #7c3aed);
  box-shadow: 0 16px 38px rgba(37, 99, 235, 0.20);
}

.vb-fetch-27-toolbar button:nth-child(3) {
  background: linear-gradient(135deg, #dc2626, #f97316);
  box-shadow: 0 16px 38px rgba(220, 38, 38, 0.18);
}

.vb-fetch-27-toolbar button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-27-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #f0fdf4;
  border: 1px solid #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-27-status.is-network {
  background: #eff6ff;
  border-color: #bfdbfe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
}

.vb-fetch-27-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-27-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-27-card {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(34, 197, 94, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-27-card span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-27-card h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-27-card p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

@media (max-width: 980px) {
  .vb-fetch-27-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 700px) {
  .vb-fetch-27-grid {
    grid-template-columns: 1fr;
  }

  .vb-fetch-27-toolbar button {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .vb-fetch-27-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API localStorage cache example is useful for dashboards, small widgets, repeated API data, blog previews, product previews, user panels, settings pages, and frontend apps where cached API data improves speed.

28. Fetch API JSON Error Handling

Fetch API JSON error handling is important because a request can fail in more than one way. The server can return an HTTP error, the response can contain invalid JSON, the network can fail, or the API can return a custom error object.

This example lets users test success, HTTP error, invalid JSON, and API error states. It uses try...catch, checks response.ok, safely parses JSON, and shows a readable UI message for each failure type.

Example 28

Fetch API JSON Error Handling

Test different API response states and show clean user-friendly error messages.

Waiting

No request tested yet

Choose a response type to test Fetch API error handling.

{}

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-28-demo");
  if (!demo) return;

  const buttons = demo.querySelectorAll("[data-vb-fetch-28-mode]");
  const result = demo.querySelector("[data-vb-fetch-28-result]");

  function fakeResponse(mode) {
    return new Promise(function (resolve) {
      setTimeout(function () {
        if (mode === "success") {
          resolve({
            ok: true,
            status: 200,
            text: function () {
              return Promise.resolve(JSON.stringify({
                id: 28,
                status: "success",
                message: "Valid JSON response parsed successfully."
              }));
            }
          });
          return;
        }

        if (mode === "http") {
          resolve({
            ok: false,
            status: 500,
            text: function () {
              return Promise.resolve(JSON.stringify({
                error: "Internal server error",
                code: "SERVER_ERROR"
              }));
            }
          });
          return;
        }

        if (mode === "invalid") {
          resolve({
            ok: true,
            status: 200,
            text: function () {
              return Promise.resolve("{ invalid json response");
            }
          });
          return;
        }

        resolve({
          ok: true,
          status: 200,
          text: function () {
            return Promise.resolve(JSON.stringify({
              success: false,
              error: "API rejected the request",
              field: "email"
            }));
          }
        });
      }, 700);
    });
  }

  async function safeJson(response) {
    const text = await response.text();

    try {
      return {
        data: JSON.parse(text),
        raw: text
      };
    } catch (error) {
      throw new Error("Invalid JSON response: " + error.message);
    }
  }

  function render(type, label, title, message, data) {
    result.className = "vb-fetch-28-result";

    if (type) {
      result.classList.add(type);
    }

    result.innerHTML =
      '<span>' + label + '</span>' +
      '<h4>' + title + '</h4>' +
      '<p>' + message + '</p>' +
      '<pre>' + JSON.stringify(data || {}, null, 2) + '</pre>';
  }

  async function runMode(mode) {
    buttons.forEach(function (button) {
      button.disabled = true;
    });

    render("", "Loading", "Checking response", "Testing the " + mode + " response mode...", {
      mode: mode
    });

    try {
      const response = await fakeResponse(mode);
      const parsed = await safeJson(response);

      if (!response.ok) {
        throw new Error("HTTP " + response.status + ": " + (parsed.data.error || "Request failed"));
      }

      if (parsed.data.success === false) {
        throw new Error(parsed.data.error || "API returned a custom error object");
      }

      render("is-success", "Success", "JSON handled correctly", "The response was parsed and accepted.", parsed.data);
    } catch (error) {
      render("is-error", "Error", "Request handled safely", error.message, {
        mode: mode,
        error: error.message
      });
    } finally {
      buttons.forEach(function (button) {
        button.disabled = false;
      });
    }
  }

  buttons.forEach(function (button) {
    button.addEventListener("click", function () {
      runMode(button.dataset.vbFetch28Mode);
    });
  });
})();

HTML

<div class="vb-fetch-28-demo">
  <div class="vb-fetch-28-shell">
    <div class="vb-fetch-28-header">
      <span>Example 28</span>
      <h3>Fetch API JSON Error Handling</h3>
      <p>Test different API response states and show clean user-friendly error messages.</p>
    </div>

    <div class="vb-fetch-28-buttons">
      <button type="button" data-vb-fetch-28-mode="success">Success JSON</button>
      <button type="button" data-vb-fetch-28-mode="http">HTTP Error</button>
      <button type="button" data-vb-fetch-28-mode="invalid">Invalid JSON</button>
      <button type="button" data-vb-fetch-28-mode="api">API Error Object</button>
    </div>

    <div class="vb-fetch-28-result" data-vb-fetch-28-result>
      <span>Waiting</span>
      <h4>No request tested yet</h4>
      <p>Choose a response type to test Fetch API error handling.</p>
      <pre>{}</pre>
    </div>
  </div>
</div>

CSS

.vb-fetch-28-demo,
.vb-fetch-28-demo * {
  box-sizing: border-box;
}

.vb-fetch-28-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(239, 68, 68, 0.14), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(59, 130, 246, 0.16), transparent 34%),
    linear-gradient(135deg, #fef2f2 0%, #eff6ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(239, 68, 68, 0.15);
  overflow: hidden;
}

.vb-fetch-28-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-28-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #7f1d1d 0%, #ef4444 48%, #2563eb 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-28-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #fee2e2 !important;
  -webkit-text-fill-color: #fee2e2 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-28-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-28-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-28-buttons {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
}

.vb-fetch-28-buttons button {
  min-height: 50px;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #ef4444, #2563eb);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 14px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(239, 68, 68, 0.18);
}

.vb-fetch-28-buttons button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-28-result {
  min-width: 0;
  margin: clamp(20px, 4vw, 34px);
  padding: clamp(20px, 4vw, 32px);
  border-radius: 28px;
  background:
    radial-gradient(circle at 14% 12%, rgba(59, 130, 246, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 18px 48px rgba(15, 23, 42, 0.08);
}

.vb-fetch-28-result.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-28-result.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-28-result span {
  display: inline-flex;
  margin-bottom: 12px;
  padding: 8px 11px;
  border-radius: 999px;
  background: #dbeafe;
  color: #1d4ed8 !important;
  -webkit-text-fill-color: #1d4ed8 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-28-result.is-success span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-28-result.is-error span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-28-result h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(30px, 5vw, 52px) !important;
  line-height: 1.02 !important;
  font-weight: 950 !important;
  letter-spacing: -0.06em;
  overflow-wrap: anywhere;
}

.vb-fetch-28-result p {
  max-width: 780px;
  margin: 0 0 16px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 16px;
  line-height: 1.65;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-28-result pre {
  max-width: 100%;
  max-height: 260px;
  overflow: auto;
  margin: 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #dbeafe !important;
  -webkit-text-fill-color: #dbeafe !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .vb-fetch-28-buttons {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .vb-fetch-28-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.045em;
  }

  .vb-fetch-28-buttons {
    grid-template-columns: 1fr;
  }
}

This Fetch API JSON error handling example is useful for production API forms, dashboards, widgets, checkout actions, admin panels, integrations, and any JavaScript interface that needs reliable error messages.

29. Fetch API Authentication Header Example

Fetch API authentication headers are used when JavaScript needs to send a token, API key, session value, or authorization string with a request. In real projects, secrets should be stored securely on the backend, but frontend demos can still show how headers are attached to a request.

This example builds a safe demo request with an authorization header, shows the generated header preview, masks the token in the UI, and sends a public API request. The token is fake and only used to demonstrate the structure.

Example 29

Fetch API Authentication Header

Send a request with a demo authorization header and display a safe masked token preview.

Waiting

No auth request sent

Enter a demo token and send a request with custom headers.

{}

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-29-demo");
  if (!demo) return;

  const tokenInput = demo.querySelector("[data-vb-fetch-29-token]");
  const clientInput = demo.querySelector("[data-vb-fetch-29-client]");
  const button = demo.querySelector("[data-vb-fetch-29-send]");
  const result = demo.querySelector("[data-vb-fetch-29-result]");

  function maskToken(token) {
    if (token.length <= 8) {
      return "********";
    }

    return token.slice(0, 4) + "..." + token.slice(-4);
  }

  function render(type, label, title, message, data) {
    result.className = "vb-fetch-29-result";

    if (type) {
      result.classList.add(type);
    }

    result.innerHTML =
      '<span>' + label + '</span>' +
      '<h4>' + title + '</h4>' +
      '<p>' + message + '</p>' +
      '<pre>' + JSON.stringify(data || {}, null, 2) + '</pre>';
  }

  async function sendAuthRequest() {
    const token = tokenInput.value.trim();
    const client = clientInput.value.trim();

    if (!token || !client) {
      render("is-error", "Error", "Missing header data", "Enter both a demo token and client name.", {});
      return;
    }

    const headersPreview = {
      Authorization: "Bearer " + maskToken(token),
      "X-Client-Name": client,
      "Content-Type": "application/json"
    };

    button.disabled = true;

    render("", "Loading", "Sending header request", "The request includes Authorization and custom client headers.", headersPreview);

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts/1", {
        method: "GET",
        headers: {
          Authorization: "Bearer " + token,
          "X-Client-Name": client,
          "Content-Type": "application/json"
        }
      });

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      const data = await response.json();

      render("is-success", "Success", "Auth-style request sent", "The demo request completed. Token is masked in the UI preview.", {
        sentHeaders: headersPreview,
        apiResponse: data
      });
    } catch (error) {
      render("is-error", "Error", "Request failed", error.message, {
        sentHeaders: headersPreview
      });
    } finally {
      button.disabled = false;
    }
  }

  button.addEventListener("click", sendAuthRequest);
})();

HTML

<div class="vb-fetch-29-demo">
  <div class="vb-fetch-29-shell">
    <div class="vb-fetch-29-header">
      <span>Example 29</span>
      <h3>Fetch API Authentication Header</h3>
      <p>Send a request with a demo authorization header and display a safe masked token preview.</p>
    </div>

    <div class="vb-fetch-29-body">
      <div class="vb-fetch-29-form">
        <label>
          Demo bearer token
          <input type="text" value="demo_token_123456789" data-vb-fetch-29-token>
        </label>

        <label>
          Custom client name
          <input type="text" value="website-demo-client" data-vb-fetch-29-client>
        </label>

        <button type="button" data-vb-fetch-29-send>Send Auth Request</button>
      </div>

      <div class="vb-fetch-29-result" data-vb-fetch-29-result>
        <span>Waiting</span>
        <h4>No auth request sent</h4>
        <p>Enter a demo token and send a request with custom headers.</p>
        <pre>{}</pre>
      </div>
    </div>
  </div>
</div>

CSS

.vb-fetch-29-demo,
.vb-fetch-29-demo * {
  box-sizing: border-box;
}

.vb-fetch-29-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(15, 23, 42, 0.12), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(99, 102, 241, 0.17), transparent 34%),
    linear-gradient(135deg, #f8fafc 0%, #eef2ff 54%, #ffffff 100%) !important;
  border: 1px solid rgba(15, 23, 42, 0.10);
  overflow: hidden;
}

.vb-fetch-29-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-29-header {
  padding: clamp(24px, 4vw, 38px);
  background:
    radial-gradient(circle at 18% 18%, rgba(255,255,255,0.12), transparent 34%),
    linear-gradient(135deg, #020617 0%, #334155 45%, #4f46e5 100%) !important;
}

.vb-fetch-29-header span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #e2e8f0 !important;
  -webkit-text-fill-color: #e2e8f0 !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-29-header h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-29-header p {
  max-width: 780px;
  margin: 0 !important;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-29-body {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: 22px;
  padding: clamp(20px, 4vw, 34px);
  min-width: 0;
}

.vb-fetch-29-form {
  display: grid;
  align-content: start;
  gap: 14px;
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background: #f8fafc;
  border: 1px solid #e2e8f0;
}

.vb-fetch-29-form label {
  display: grid;
  gap: 8px;
  color: #334155 !important;
  -webkit-text-fill-color: #334155 !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-29-form input {
  width: 100%;
  min-width: 0;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid #cbd5e1;
  border-radius: 18px;
  background: #ffffff;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 800;
  outline: none;
}

.vb-fetch-29-form button {
  min-height: 52px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(135deg, #334155, #4f46e5);
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(51, 65, 85, 0.22);
}

.vb-fetch-29-form button:disabled {
  opacity: 0.65;
  cursor: not-allowed;
}

.vb-fetch-29-result {
  min-width: 0;
  padding: 20px;
  border-radius: 24px;
  background:
    radial-gradient(circle at 14% 12%, rgba(79, 70, 229, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
}

.vb-fetch-29-result.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.vb-fetch-29-result.is-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.vb-fetch-29-result span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #e0e7ff;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-29-result.is-success span {
  background: #dcfce7;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-29-result.is-error span {
  background: #fee2e2;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-29-result h4 {
  margin: 0 0 10px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(26px, 4vw, 44px) !important;
  line-height: 1.05 !important;
  font-weight: 950 !important;
  letter-spacing: -0.05em;
  overflow-wrap: anywhere;
}

.vb-fetch-29-result p {
  margin: 0 0 14px !important;
  color: #475569 !important;
  -webkit-text-fill-color: #475569 !important;
  font-size: 15px;
  line-height: 1.65;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-29-result pre {
  max-width: 100%;
  max-height: 260px;
  overflow: auto;
  margin: 0 !important;
  padding: 14px;
  border-radius: 16px;
  background: #0f172a;
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 860px) {
  .vb-fetch-29-body {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-29-header h3 {
    font-size: 36px !important;
    letter-spacing: -0.045em;
  }
}

This Fetch API authentication header example is useful for protected endpoints, SaaS dashboards, backend integrations, API clients, admin tools, user portals, and any JavaScript request that needs custom headers.

30. Complete Responsive Fetch API Dashboard Section

A complete responsive Fetch API dashboard combines several common API patterns into one practical section: loading data, search, filtering, refresh, status cards, error handling, and responsive result rendering.

This final example loads API data, stores it in local state, lets users search and filter the visible cards, refresh the API data, and shows a full responsive dashboard layout that can be adapted for real admin panels and SaaS interfaces.

Example 30

Complete Responsive Fetch API Dashboard

Load API data, search it, filter it, refresh it, and render a complete dashboard section.

Total posts--
Visible cards--
Active userAll
Dashboard is ready. Loading API data...

JavaScript

(function () {
  const demo = document.querySelector(".vb-fetch-30-demo");
  if (!demo) return;

  const refreshButton = demo.querySelector("[data-vb-fetch-30-refresh]");
  const searchInput = demo.querySelector("[data-vb-fetch-30-search]");
  const userSelect = demo.querySelector("[data-vb-fetch-30-user]");
  const status = demo.querySelector("[data-vb-fetch-30-status]");
  const stats = demo.querySelector("[data-vb-fetch-30-stats]");
  const grid = demo.querySelector("[data-vb-fetch-30-grid]");

  let posts = [];

  function setStatus(text, type) {
    status.className = "vb-fetch-30-status";

    if (type) {
      status.classList.add(type);
    }

    status.textContent = text;
  }

  function updateStats(visiblePosts) {
    const activeUser = userSelect.value === "all" ? "All" : "User " + userSelect.value;

    stats.innerHTML =
      '<div><span>Total posts</span><strong>' + posts.length + '</strong></div>' +
      '<div><span>Visible cards</span><strong>' + visiblePosts.length + '</strong></div>' +
      '<div><span>Active user</span><strong>' + activeUser + '</strong></div>';
  }

  function renderCards(visiblePosts) {
    if (!visiblePosts.length) {
      grid.innerHTML = '<div class="vb-fetch-30-empty">No matching dashboard records found. Try another search or filter.</div>';
      return;
    }

    grid.innerHTML = visiblePosts.slice(0, 9).map(function (post) {
      return (
        '<article class="vb-fetch-30-card">' +
          '<span>User #' + post.userId + ' · Post #' + post.id + '</span>' +
          '<h4>' + post.title + '</h4>' +
          '<p>' + post.body + '</p>' +
        '</article>'
      );
    }).join("");
  }

  function applyFilters() {
    const query = searchInput.value.trim().toLowerCase();
    const user = userSelect.value;

    const visiblePosts = posts.filter(function (post) {
      const matchesUser = user === "all" || String(post.userId) === user;
      const matchesSearch = !query || post.title.toLowerCase().includes(query) || post.body.toLowerCase().includes(query);

      return matchesUser && matchesSearch;
    });

    updateStats(visiblePosts);
    renderCards(visiblePosts);

    if (posts.length) {
      setStatus("Showing " + Math.min(visiblePosts.length, 9) + " of " + visiblePosts.length + " matching records.", "is-success");
    }
  }

  async function loadDashboardData() {
    refreshButton.disabled = true;
    grid.innerHTML = "";
    setStatus("Loading dashboard API data...", "");

    try {
      const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=30");

      if (!response.ok) {
        throw new Error("HTTP status " + response.status);
      }

      posts = await response.json();

      applyFilters();
      setStatus("Dashboard loaded " + posts.length + " API records successfully.", "is-success");
    } catch (error) {
      posts = [];
      updateStats([]);
      grid.innerHTML = "";
      setStatus("Dashboard request failed: " + error.message, "is-error");
    } finally {
      refreshButton.disabled = false;
    }
  }

  refreshButton.addEventListener("click", loadDashboardData);
  searchInput.addEventListener("input", applyFilters);
  userSelect.addEventListener("change", applyFilters);

  loadDashboardData();
})();

HTML

<div class="vb-fetch-30-demo">
  <div class="vb-fetch-30-shell">
    <div class="vb-fetch-30-hero">
      <div>
        <span>Example 30</span>
        <h3>Complete Responsive Fetch API Dashboard</h3>
        <p>Load API data, search it, filter it, refresh it, and render a complete dashboard section.</p>
      </div>

      <button type="button" data-vb-fetch-30-refresh>Refresh API Data</button>
    </div>

    <div class="vb-fetch-30-stats" data-vb-fetch-30-stats>
      <div><span>Total posts</span><strong>--</strong></div>
      <div><span>Visible cards</span><strong>--</strong></div>
      <div><span>Active user</span><strong>All</strong></div>
    </div>

    <div class="vb-fetch-30-controls">
      <label>
        Search dashboard data
        <input type="search" placeholder="Search titles or content..." data-vb-fetch-30-search>
      </label>

      <label>
        Filter by user
        <select data-vb-fetch-30-user>
          <option value="all">All users</option>
          <option value="1">User 1</option>
          <option value="2">User 2</option>
          <option value="3">User 3</option>
        </select>
      </label>
    </div>

    <div class="vb-fetch-30-status" data-vb-fetch-30-status>
      Dashboard is ready. Loading API data...
    </div>

    <div class="vb-fetch-30-grid" data-vb-fetch-30-grid></div>
  </div>
</div>

CSS

.vb-fetch-30-demo,
.vb-fetch-30-demo * {
  box-sizing: border-box;
}

.vb-fetch-30-demo {
  margin: 28px 0;
  padding: clamp(18px, 4vw, 38px);
  border-radius: 34px;
  background:
    radial-gradient(circle at 12% 16%, rgba(99, 102, 241, 0.17), transparent 32%),
    radial-gradient(circle at 88% 12%, rgba(34, 197, 94, 0.16), transparent 34%),
    linear-gradient(135deg, #eef2ff 0%, #f0fdf4 54%, #ffffff 100%) !important;
  border: 1px solid rgba(99, 102, 241, 0.16);
  overflow: hidden;
}

.vb-fetch-30-shell {
  max-width: 1120px;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 30px;
  background: #ffffff;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 30px 90px rgba(15, 23, 42, 0.12);
}

.vb-fetch-30-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 20px;
  align-items: end;
  padding: clamp(24px, 4vw, 38px);
  background:
    linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(0deg, rgba(255,255,255,0.08) 1px, transparent 1px),
    linear-gradient(135deg, #312e81 0%, #6366f1 50%, #16a34a 100%) !important;
  background-size: 24px 24px, 24px 24px, auto !important;
}

.vb-fetch-30-hero span {
  display: inline-flex;
  margin-bottom: 14px;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255,255,255,0.14);
  color: #e0e7ff !important;
  -webkit-text-fill-color: #e0e7ff !important;
  font-size: 12px;
  font-weight: 950;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.vb-fetch-30-hero h3 {
  max-width: 100%;
  margin: 0 0 14px !important;
  color: #ffffff !important;
  -webkit-text-fill-color: #ffffff !important;
  font-size: clamp(32px, 5vw, 62px) !important;
  line-height: 0.96 !important;
  font-weight: 950 !important;
  letter-spacing: -0.07em;
  overflow-wrap: anywhere;
}

.vb-fetch-30-hero p {
  max-width: 780px;
  margin: 0 !important;
  color: #dcfce7 !important;
  -webkit-text-fill-color: #dcfce7 !important;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 650;
}

.vb-fetch-30-hero button {
  min-height: 52px;
  padding: 0 20px;
  border: 0;
  border-radius: 999px;
  background: #ffffff;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 15px;
  font-weight: 950;
  cursor: pointer;
  box-shadow: 0 16px 38px rgba(15, 23, 42, 0.18);
}

.vb-fetch-30-hero button:disabled {
  opacity: 0.7;
  cursor: not-allowed;
}

.vb-fetch-30-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
}

.vb-fetch-30-stats div {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(99, 102, 241, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-30-stats span {
  display: block;
  margin-bottom: 8px;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 12px;
  font-weight: 950;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.vb-fetch-30-stats strong {
  display: block;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: clamp(28px, 4vw, 44px);
  line-height: 1;
  font-weight: 950;
  overflow-wrap: anywhere;
}

.vb-fetch-30-controls {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(220px, 0.8fr);
  gap: 14px;
  padding: clamp(20px, 4vw, 34px) clamp(20px, 4vw, 34px) 0;
  min-width: 0;
}

.vb-fetch-30-controls label {
  display: grid;
  gap: 8px;
  min-width: 0;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 14px;
  font-weight: 900;
}

.vb-fetch-30-controls input,
.vb-fetch-30-controls select {
  width: 100%;
  min-width: 0;
  min-height: 52px;
  padding: 0 14px;
  border: 1px solid #c7d2fe;
  border-radius: 18px;
  background: #f8fafc;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 15px;
  font-weight: 800;
  outline: none;
}

.vb-fetch-30-status {
  margin: 16px clamp(20px, 4vw, 34px) 0;
  min-width: 0;
  padding: 14px 16px;
  border-radius: 18px;
  background: #eef2ff;
  border: 1px solid #c7d2fe;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 14px;
  line-height: 1.5;
  font-weight: 850;
  overflow-wrap: anywhere;
}

.vb-fetch-30-status.is-success {
  background: #f0fdf4;
  border-color: #bbf7d0;
  color: #166534 !important;
  -webkit-text-fill-color: #166534 !important;
}

.vb-fetch-30-status.is-error {
  background: #fef2f2;
  border-color: #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
}

.vb-fetch-30-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  min-width: 0;
  padding: clamp(20px, 4vw, 34px);
}

.vb-fetch-30-card {
  min-width: 0;
  padding: 20px;
  border-radius: 22px;
  background:
    radial-gradient(circle at 14% 12%, rgba(99, 102, 241, 0.10), transparent 34%),
    linear-gradient(135deg, #ffffff, #f8fafc) !important;
  border: 1px solid rgba(148, 163, 184, 0.22);
  box-shadow: 0 14px 34px rgba(15, 23, 42, 0.06);
}

.vb-fetch-30-card span {
  display: inline-flex;
  margin-bottom: 10px;
  padding: 7px 10px;
  border-radius: 999px;
  background: #e0e7ff;
  color: #4338ca !important;
  -webkit-text-fill-color: #4338ca !important;
  font-size: 11px;
  font-weight: 950;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.vb-fetch-30-card h4 {
  margin: 0 0 8px !important;
  color: #0f172a !important;
  -webkit-text-fill-color: #0f172a !important;
  font-size: 18px !important;
  line-height: 1.25 !important;
  font-weight: 950 !important;
  overflow-wrap: anywhere;
}

.vb-fetch-30-card p {
  margin: 0 !important;
  color: #64748b !important;
  -webkit-text-fill-color: #64748b !important;
  font-size: 14px;
  line-height: 1.55;
  font-weight: 650;
  overflow-wrap: anywhere;
}

.vb-fetch-30-empty {
  grid-column: 1 / -1;
  padding: 22px;
  border-radius: 22px;
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: #b91c1c !important;
  -webkit-text-fill-color: #b91c1c !important;
  font-size: 15px;
  line-height: 1.55;
  font-weight: 850;
  text-align: center;
}

@media (max-width: 980px) {
  .vb-fetch-30-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .vb-fetch-30-hero {
    grid-template-columns: 1fr;
  }

  .vb-fetch-30-hero button {
    width: 100%;
  }
}

@media (max-width: 760px) {
  .vb-fetch-30-stats,
  .vb-fetch-30-controls,
  .vb-fetch-30-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 640px) {
  .vb-fetch-30-hero h3 {
    font-size: 36px !important;
    letter-spacing: -0.045em;
  }
}

This complete responsive Fetch API dashboard section is useful for admin panels, SaaS dashboards, API data tools, user portals, content dashboards, CRM overviews, and production-style frontend interfaces that combine loading, filtering, searching, and rendering API data.

EntitySchema Pro WordPress structured data and entity SEO plugin
WordPress SEO Plugin

EntitySchema Pro

Build a real entity-based structured data system for WordPress, modern SEO, Google visibility, and AI-readable websites.

89€ 29€ Save 60€
View Plugin

JavaScript Fetch API Best Practices

Good Fetch API code is not only about making a request. A real website needs reliable loading states, safe error handling, readable response parsing, clear UI feedback, and request logic that does not break when the API returns an unexpected result. These best practices help you build Fetch API features that work better in production websites, WordPress integrations, SaaS dashboards, ecommerce tools, and custom admin panels.

01

Always check response.ok

Fetch does not automatically throw an error for HTTP status codes like 404 or 500. Check response.ok before parsing and rendering data.

02

Use clear loading states

Disable buttons, show progress text, and let users know that data is being requested. This prevents duplicate clicks and confusing UI states.

03

Keep request logic scoped

Use unique class names, data attributes, and local variables so multiple Fetch API demos or widgets can run on the same page without conflicts.

04

Protect private tokens

Never expose real private API keys inside frontend JavaScript. Use a backend endpoint, proxy route, or server-side integration for sensitive credentials.

When you build a real Fetch API feature, think about the full user journey. What happens before the request starts? What should the user see while waiting? What happens if the request fails? What should happen if the response is empty? What should happen if the user clicks twice, changes a filter, or leaves the page? A strong Fetch API implementation handles these details instead of only showing the happy path.

Fetch API Error Handling Tips

Error handling is one of the most important parts of a good Fetch API example. Many beginners expect fetch() to automatically fail when an API returns a 404, 500, or validation error, but Fetch only rejects on network-level failures. That means you must check the response status yourself and decide what message to show to the user.

A professional Fetch API feature should handle at least four types of problems: network errors, HTTP status errors, invalid JSON responses, and API-level error objects. It should also explain the issue in plain language so users are not left looking at a broken form, empty dashboard, or frozen loading screen.

Simple production-style Fetch API error pattern

async function getApiData(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error("Request failed with status " + response.status);
    }

    const data = await response.json();

    return {
      success: true,
      data: data
    };
  } catch (error) {
    return {
      success: false,
      message: error.message
    };
  }
}

For user-facing interfaces, avoid showing raw technical errors only. A developer console can contain detailed logs, but the page itself should show a clean message such as “Could not load products. Please try again.” or “Your message could not be sent. Check the form and try again.” This makes the interface feel stable even when the API fails.

Common Fetch API Mistakes

Most Fetch API bugs happen because the code only handles the perfect response. Real APIs can be slow, unavailable, empty, protected, rate-limited, or formatted differently than expected. Avoiding the following mistakes will make your JavaScript API examples and production code much stronger.

A good rule is simple: every Fetch API feature should answer what happens when the request succeeds, fails, returns no data, takes too long, or gets triggered again before the first request is finished.

JavaScript Fetch API FAQ

The JavaScript Fetch API is used to make HTTP requests from the browser. It can load JSON data, submit forms, send POST requests, update records, delete items, request search results, connect to backend endpoints, and power dynamic dashboards or API-connected website features.

Fetch API is usually easier to read and more modern than XMLHttpRequest, especially when used with promises and async/await. XMLHttpRequest can still be useful for upload progress events, but Fetch API is the standard choice for many modern JSON and API request workflows.

No. Fetch API only rejects automatically for network-level failures. For HTTP errors like 404 or 500, you should check response.ok or response.status and throw your own error when needed.

Yes. Fetch API can send JSON data, regular form data, and FormData objects. For JSON requests, use JSON.stringify() and the Content-Type: application/json header. For file uploads or multipart forms, use FormData.

Yes. Fetch API works well with WordPress REST API endpoints, custom plugin endpoints, admin AJAX routes, form handlers, product data, search features, dashboards, and frontend tools. For protected requests, use nonces and backend validation.

No. Real private API keys should not be stored in frontend JavaScript because visitors can inspect the code. Use a backend endpoint, server-side proxy, WordPress plugin setting, or secure server environment variable instead.

Conclusion

The JavaScript Fetch API is one of the most useful skills for building modern interactive websites. Once you understand GET requests, POST requests, JSON handling, form submissions, headers, error states, query parameters, request cancellation, caching, retry logic, and dashboard rendering, you can connect almost any frontend interface to real backend data.

The examples in this guide are designed to be practical and reusable. You can adapt them for WordPress plugins, custom forms, ecommerce product feeds, quote request systems, SaaS dashboards, admin panels, search interfaces, user portals, API widgets, and real business websites. The most important part is not only making the request, but handling the entire request lifecycle in a way that feels stable for the user.

For best results, start with a simple GET request, then add loading states, error handling, validation, filtering, caching, and safe rendering step by step. This approach keeps your JavaScript easier to debug and makes your Fetch API features more reliable in real projects.