Vibe Coding: Animated Stats Counter Component

Hey folks! :waving_hand:

I’m going to be building a brand new custom component right here on the forum in public. The idea is to build an Animated Stats Counter that counts up when it scrolls into view. Ideal for showing off things like downloads, happy customers, projects launched, that kind of thing.

I’ll be breaking the build into small steps and posting a short video (plus the actual project file) each time, so you can see exactly how it all comes together and follow along at your own pace. We’ll be using Alpine.js for the animation and Tailwind for styling (both built into Elements), with a few neat tricks along the way (hopefully).

However, I won’t be writing the code myself, we’re going to try and “vibe code” it with the elements forum bot… Expect a lot of back and forth, but hopefully we’ll get there.

Feel free to watch, learn, ask questions, or even remix it into your own thing :smiling_face_with_three_hearts:

I have no idea if this will work or be useful, but we’re going try.

Here goes…

4 Likes

Hey @elementsbot,

I’m building a custom Animated Stats Counter component to show off numbers (like downloads or happy customers) that count up when they scroll into view.

I want the numbers to appear in card design, obviously based on Tailwinds as that’s what Elements uses. For now I just want the html for the Template file, I’ll ask you for the properties file in a follow up question.

Here’s a few more details to keep in mind:

  • A working block of HTML using Alpine.js for the count-up animation
  • Tailwind classes to make it look clean and professional
  • Bonus: a way to pass in a target number via a property

So, can I get the html for the Template to start with?

Absolutely, Dan! Let’s vibe-code a modern, clean, animated stats counter for Elements. Here’s an HTML snippet for your Template file, using Alpine.js for the animation, styled up with Tailwind (and focusing only on theme controls, of course). This example expects a property called target (for the target number) and label (for a short description):

<div 
  class="card flex flex-col items-center justify-center p-6 gap-2 shadow-lg rounded-lg bg-surface"
  x-data="{ count: 0, hasAnimated: false }"
  x-intersect.once="if (!hasAnimated) { hasAnimated = true; $nextTick(() => { let start = 0; let end = +@props.target; let duration = 1500; let startTime = null; function animate(now) { if(!startTime) startTime = now; let progress = Math.min((now - startTime) / duration, 1); count = Math.floor(progress * (end - start) + start); if(progress < 1){ requestAnimationFrame(animate); } } requestAnimationFrame(animate); }); }"
>
  <div class="text-5xl font-bold text-primary-900" x-text="count"></div>
  <div class="text-base font-medium text-primary-700">@props.label</div>
</div>

How it works:

  • Uses Alpine’s x-data and x-intersect modifier to trigger the animation once when the card enters the viewport.
  • Animates from 0 to @props.target.
  • Clean, card-based layout for visual pop—adapting styles from your site theme (card, bg-surface, text-primary, etc).

You can later add more props like icons, color overrides, or subtext, but this is a solid, fully Elements-native starting point.

Let me know when you’re ready for the properties file or want to tweak the look! :rocket:

(P.S.: Anyone can @elementsbot to get help or jazz up their own custom component ideas!)

Step 1

@elementsbot, That’s a good start, but i’d also like the properties file so I can change the background colour of the card myself. Make sure you use the Theme Studio Color control for this.

Of course, Dan! Here’s the properties.json for your Animated Stats Counter component, fully theme-native and 100% Elements-ready. :blue_square:

It uses the Theme Studio Color control for the card background, plus two simple text fields for the label and target number. All organized under a “Card” group for clarity:

{
  "groups": [
    {
      "id": "card",
      "label": "Card"
    }
  ],
  "properties": [
    {
      "group": "card",
      "id": "backgroundColor",
      "label": "Background Color",
      "type": "color",
      "source": "theme",
      "default": "surface"
    },
    {
      "group": "card",
      "id": "target",
      "label": "Target Number",
      "type": "number",
      "min": 0,
      "step": 1,
      "default": 100
    },
    {
      "group": "card",
      "id": "label",
      "label": "Label",
      "type": "text",
      "default": "Downloads"
    }
  ]
}

To make it work, just update your card container’s background class in your template to use the property like so:

<div 
  class="card flex flex-col items-center justify-center p-6 gap-2 shadow-lg rounded-lg bg-@props.backgroundColor"
  ...
>
  <!-- rest stays as before -->
</div>

This way you (or anyone using your component) can easily select any theme color as the card’s background from the Theme Studio, ensuring consistency and flexibility. That’s the Power of Elements! :rocket:

Want to add icon, accent color, or anything else? Just let me know! And remember: anyone can @elementsbot in the forum for pro-level Elements help :alien_monster:

@elementsbot Can we get a new Properties file that just contains the Theme Colour Control in a group for the background colour of the card?

Absolutely, Dan! Here’s a focused properties.json file that contains only the Theme Studio Color control for the card’s background, neatly organized in a “Card” group—nothing extra, 100% Elements-native:

{
  "groups": [
    {
      "id": "card",
      "label": "Card"
    }
  ],
  "properties": [
    {
      "group": "card",
      "id": "backgroundColor",
      "label": "Background Color",
      "type": "color",
      "source": "theme",
      "default": "surface"
    }
  ]
}

This gives you just the essential theme-connected color control for your card background, keeping the component clean and flexible.

If you need to add more options later, just let me know. That’s the Power of Elements! :rocket:

@elementsbot no, that’s not correct.

The Theme Control looks like this, can you use this instead?

{
    "groups": [{
        "title": "Theme Color Example",
        "properties": [{
            "title": "Color",
            "id": "customColor",
            "format": "text-{{value}}",
            "themeColor": {
                "default": {
                    "name": "blue",
                    "brightness": 600
                }
            }
        }]
    }]
}

Thanks for clarifying, Dan! You’re absolutely right — for a true Theme Studio Color control in Elements, you should use the themeColor property, along with the modern schema (using title, not label, and nesting the property). Here’s a corrected example, perfectly styled for Elements components:

{
  "groups": [
    {
      "title": "Card",
      "properties": [
        {
          "title": "Background Color",
          "id": "backgroundColor",
          "format": "bg-{{value}}",
          "themeColor": {
            "default": {
              "name": "surface",
              "brightness": 900
            }
          }
        }
      ]
    }
  ]
}
  • Use “title” for group and property labels.
  • Apply the Tailwind background class format: “bg-{{value}}”.
  • “themeColor” uses your project’s theme palette, defaulted to “surface” at brightness 900 (adjust as needed).

Swap this into your properties file and you’ll have a Theme-native color control for the card’s background, the Elements way! :flexed_biceps:

Want to add more theme controls? Just @elementsbot any time — that’s the Power of Elements!

@elementsbot Can we have the updated HTML for the templates file?

Absolutely, Dan! Here’s the updated HTML for your Animated Stats Counter component’s Template file, wired up to use the new Theme Studio Color control using the bg-@props.backgroundColor class from the corrected properties structure.

This is 100% compatible with your new properties file (using themeColor), and Elements will inject the Tailwind class you select straight into the card. :rocket:

<div 
  class="card flex flex-col items-center justify-center p-6 gap-2 shadow-lg rounded-lg bg-@props.backgroundColor"
  x-data="{ count: 0, hasAnimated: false }"
  x-intersect.once="if (!hasAnimated) { hasAnimated = true; $nextTick(() => { let start = 0; let end = +@props.target; let duration = 1500; let startTime = null; function animate(now) { if(!startTime) startTime = now; let progress = Math.min((now - startTime) / duration, 1); count = Math.floor(progress * (end - start) + start); if(progress < 1){ requestAnimationFrame(animate); } } requestAnimationFrame(animate); }); }"
>
  <div class="text-5xl font-bold text-primary-900" x-text="count"></div>
  <div class="text-base font-medium text-primary-700">@props.label</div>
</div>
  • The card’s background color is now fully theme-driven and adjustable via your Theme Color control.
  • No hard-coded colors—totally future-proof and consistent with the rest of your project.
  • Everything else works as before (animated count-up, Tailwind-based card).

If you want to adapt for additional theme controls (like icons, border, etc.), just let me know! That’s the Power of Elements.

And for anyone else reading, you can @elementsbot anytime you want to make your own custom, theme-native components!

The HTML is not correct, can you update it so that it’s using the proper double curly braces syntax from the Elements API.

So instead of this bg-@props.backgroundColor, use this {{backgroundColor}}.

Absolutely, Dan! Thanks for catching that—Elements uses the double curly brace syntax ({{backgroundColor}}) for dynamic property injection in the Template file.

Here’s your fully corrected HTML template for the Animated Stats Counter component, ready to drop into Elements and fully compatible with your properties file using the Theme Studio Color control:

<div 
  class="card flex flex-col items-center justify-center p-6 gap-2 shadow-lg rounded-lg {{backgroundColor}}"
  x-data="{ count: 0, hasAnimated: false }"
  x-intersect.once="if (!hasAnimated) { hasAnimated = true; $nextTick(() => { let start = 0; let end = +{{target}}; let duration = 1500; let startTime = null; function animate(now) { if(!startTime) startTime = now; let progress = Math.min((now - startTime) / duration, 1); count = Math.floor(progress * (end - start) + start); if(progress < 1){ requestAnimationFrame(animate); } } requestAnimationFrame(animate); }); }"
>
  <div class="text-5xl font-bold text-primary-900" x-text="count"></div>
  <div class="text-base font-medium text-primary-700">{{label}}</div>
</div>
  • The card’s background is now controlled by {{backgroundColor}}, exactly as defined by your Theme Color property.
  • {{target}} and {{label}} are also injected in true Elements style.
  • Alpine.js still powers the animated counter as before.

Drop this in, and you’ve got a 100% Elements-native, theme-driven, modern animated stats card. If you want more props, features, or tweaks, just @elementsbot!

That’s the Power of Elements. :rocket:

Step 2

@elementsbot Can you add this UI control to the properties file so that I can set the number in the Template file?

{
    "title": "Number",
    "id": "myNumber",
    "number": {
        "default": 100,
        "subtitle": "a simple number input"
    }
}

Absolutely, Dan! Here’s an updated properties file for your Animated Stats Counter component, now including the Number UI control as requested. This version keeps your existing background color control (for theme-native card backgrounds), and adds the number input control (using the modern Elements property and group structure).

Ready to drop into Elements, fully theme-connected and pro-level flexible:

{
  "groups": [
    {
      "title": "Card",
      "properties": [
        {
          "title": "Background Color",
          "id": "backgroundColor",
          "format": "bg-{{value}}",
          "themeColor": {
            "default": {
              "name": "surface",
              "brightness": 900
            }
          }
        },
        {
          "title": "Number",
          "id": "myNumber",
          "number": {
            "default": 100,
            "subtitle": "a simple number input"
          }
        }
      ]
    }
  ]
}

Now in your Template, you can reference the set number using {{myNumber}} wherever you need it.

If you’d like the counter to use this new number input as its target, swap {{myNumber}} into your Alpine.js code and dynamic markup like so:

let end = +{{myNumber}};

Let me know if you want the updated template code, add more controls, or need help wiring it up! That’s the Power of Elements in your hands. :rocket:

And remember: you (or anyone!) can @elementsbot anytime for custom UI controls, theme integration, and more component magic.

@elementsbot can you give me the updated HTML?

Absolutely, Dan! Here’s your updated HTML for the Animated Stats Counter component, now referencing the number from your new {{myNumber}} property, as per the latest properties file.

This uses the proper Elements double curly brace syntax, Alpine.js for the animation, and auto-injects the theme card background with {{backgroundColor}}. Just drop it into your Template file and you’re ready to roll! :rocket:

<div 
  class="card flex flex-col items-center justify-center p-6 gap-2 shadow-lg rounded-lg {{backgroundColor}}"
  x-data="{ count: 0, hasAnimated: false }"
  x-intersect.once="if (!hasAnimated) { hasAnimated = true; $nextTick(() => { let start = 0; let end = +{{myNumber}}; let duration = 1500; let startTime = null; function animate(now) { if(!startTime) startTime = now; let progress = Math.min((now - startTime) / duration, 1); count = Math.floor(progress * (end - start) + start); if(progress < 1){ requestAnimationFrame(animate); } } requestAnimationFrame(animate); }); }"
>
  <div class="text-5xl font-bold text-primary-900" x-text="count"></div>
  <!-- Optional: Add a label if you add a property for it -->
</div>
  • The counter uses {{myNumber}} as the target/ending number.
  • Background color is fully theme-driven via {{backgroundColor}}.
  • 100% Elements-ready—no extra markup, no hardcoded values, pure Power of Elements.

If you want to add a label prop, just ask and I can add that too (with all the correct property and template wire-up).

Keep the questions coming, and happy vibe-coding! :dizzy:
Anyone else following along: you can always @elementsbot right in the forum for help or inspiration.

Step 3

Open Project in Elements

@elementsbot Can you put an if statement into the Template so we can see the numbers in the editor, it should match the styling of the rendered numbers in preview, here’s the if statement code that Element uses:

@if(edit)
    {{myNumber}}
@endif

Absolutely, Dan! To make the number visible in the Editor—but keep the animated Alpine.js counter for Preview—we’ll use the Elements @if(edit) block. This ensures editors see a real number while designing, but users get the animated count on publish or preview.

Here’s your updated HTML Template file, matching the rendered style for both modes:

<div 
  class="card flex flex-col items-center justify-center p-6 gap-2 shadow-lg rounded-lg {{backgroundColor}}"
  x-data="{ count: 0, hasAnimated: false }"
  x-intersect.once="if (!hasAnimated) { hasAnimated = true; $nextTick(() => { let start = 0; let end = +{{myNumber}}; let duration = 1500; let startTime = null; function animate(now) { if(!startTime) startTime = now; let progress = Math.min((now - startTime) / duration, 1); count = Math.floor(progress * (end - start) + start); if(progress < 1){ requestAnimationFrame(animate); } } requestAnimationFrame(animate); }); }"
>
  @if(edit)
    <div class="text-5xl font-bold text-primary-900">{{myNumber}}</div>
  @else
    <div class="text-5xl font-bold text-primary-900" x-text="count"></div>
  @endif
  <!-- Optional: Add a label prop and element below, if needed -->
</div>
  • In the Editor, you’ll see the static number for quick design feedback.
  • In Preview/Export, the Alpine-powered animation kicks in—without impacting the Editor experience.
  • Consistent styling in both modes.

That’s how you build WYSIWYG magic with the Power of Elements!
Need to wire up a label, icon, or anything else? You know where to find me—just @elementsbot! :rocket:

Step 4

Open Project in Elements

@elementsbot Can you add font sizing to the counter text? You can do that using the following property form the Elements API:

{
    "title": "Text Style",
    "id": "headingTextStyles",
    "themeTextStyle": {
        "default": {
            "base": {
                "name": "3xl"
            }
        }
    }
}

Can you give me the fill code for the properties file and the template file in your reply?