Template

Animated counter widget for MCP Apps

A few big numbers that count up from zero and slide in one after another. It is the widget for results worth celebrating, not for monitoring.

Uses GSAP

Add a UI to your MCP server

We just wrapped the first year of our mentoring program. Can you show the highlights?

Used show_milestones

The house design: black outline, butter yellow and a marker swipe under the title.

When to use an animated counter widget

Some numbers deserve a moment. A year-in-review, the first week after a launch, the end of a campaign: the point is not to compare against last month, it is to show what was achieved. Counting up from zero turns three figures into a small reveal.

Use it for:

  • Year-in-review or quarter recaps: people reached, events held, hours saved.
  • Launch results: signups, downloads, countries, in the first days.
  • Community milestones: members, contributors, pull requests merged.
  • Project close-outs that a team will screenshot and share.

If the numbers need a trend arrow and a comparison with the previous period, use the metrics template instead. Milestones is for celebrating; metrics is for checking.

How the model fills it

The schema asks for a title and a list of items, with at least one. Each item needs a label and a numeric value, plus three optional fields:

  • decimals (0 to 3) for figures like a rating out of ten.
  • suffix, shown after the number in a smaller size: a percent sign, "k", a unit such as "km".
  • note, a short line of context under the label, such as where the figure comes from.

value is a number, not a formatted string. That matters: the widget formats it for the language the host reports, with the right thousands separator and decimal mark, and it needs a real number to animate. Because the schema types it as a number, the model sends 4800, not "4,800". An optional kicker sits above the title.

The tool description tells the model to use the widget for year-in-review summaries, launch results and achievements. It works best with three to five items; past that, the cascade gets long.

What it does in the chat

The tiles rise into place one after another, and every number rolls up from zero to its value in about a second and a half, easing out at the end. Each tile takes the next color of the design's palette for its marker.

The animation runs on GSAP, loaded as a pinned ES module from jsDelivr through the widget's import map. Because the widget uses a library, its resource declares https://cdn.jsdelivr.net in its CSP (resourceDomains and connectDomains), and the export carries that in the manifest. Hosts block undeclared origins, so this is what keeps the animation working in ChatGPT and Claude. The CSP guide explains why.

With reduced motion on, the script skips the timeline entirely and the final numbers, already rendered by the template, simply appear. When new data arrives, the old timeline is killed before the new one starts.

No host actions are declared.

Make it yours

It wears every design. In Nordic the numerals are light and oversized; Aurora lights each glass tile from a corner; Brutalist turns tiles into flat color blocks tilted like stickers; Editorial sets ruled columns of Didot figures like a magazine sidebar; Neon makes each digit glow in its own color.

The timeline is a few lines of script: change the stagger, the duration or the easing there. Add a data-wg-message to each tile so a click asks the model for the story behind that number.

Take it to your server

Link the published widget from your MCP server so changes reach it without a deploy, or download the HTML and manifest. The guide to adding a UI to your MCP server covers both. To have your agent create and publish it, see creating widgets with an AI agent.

The data it takes

FieldTypeWhat it is
kickerstring
titlerequiredstring
itemsrequiredobject[]
└ labelrequiredstring
└ valuerequirednumber
└ decimalsinteger
└ suffixstringShown after the number, e.g. "%" or "k"
└ notestring

Sample data

{
  "kicker": "Sample data",
  "title": "A year of training",
  "items": [
    {
      "label": "People trained",
      "value": 1240,
      "note": "across 38 workshops"
    },
    {
      "label": "Satisfaction",
      "value": 9.4,
      "decimals": 1,
      "suffix": "/10"
    },
    {
      "label": "Hours saved",
      "value": 6,
      "suffix": "h/week",
      "note": "per person, from a survey"
    }
  ]
}

The tool that shows it

A widget made from this template is shown by one tool. The model reads its description to decide when to call it and fills the input schema with the data. This is the definition Widgetry exports:

{
  "name": "show_milestones",
  "title": "Milestones",
  "description": "Celebrates a few headline numbers: each one counts up from zero with its label and an optional note. Use it for year-in-review summaries, launch results or any achievement worth celebrating.",
  "inputSchema": {
    "type": "object",
    "required": [
      "title",
      "items"
    ],
    "properties": {
      "kicker": {
        "type": "string"
      },
      "title": {
        "type": "string"
      },
      "items": {
        "type": "array",
        "minItems": 1,
        "items": {
          "type": "object",
          "required": [
            "label",
            "value"
          ],
          "properties": {
            "label": {
              "type": "string"
            },
            "value": {
              "type": "number"
            },
            "decimals": {
              "type": "integer",
              "minimum": 0,
              "maximum": 3
            },
            "suffix": {
              "type": "string",
              "description": "Shown after the number, e.g. \"%\" or \"k\""
            },
            "note": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}

Your first widget, in the chat in minutes

Pick a template, drop in your data and see it as ChatGPT or Claude will show it. Then link it from your MCP server, or let your agent build the next one.