GitHub Stars Animation
A small piece of social proof for a README, a landing page or a project card. Five avatars overlap into a stack, each one entering a beat after the last, and the star count climbs from zero to the repository total over two seconds.
The component can fetch the numbers itself from a repository, or take
stargazers and starCount directly. Providing both skips the request
entirely, which is what the examples do, so a docs page or a test never depends
on the GitHub API being reachable.
Usage
Pass a count and a list of stargazers with login, avatar_url and html_url.
The avatar stack is capped by maxAvatars.
<template>
<div class="flex w-full items-center justify-center p-6">
<MGitHubStarsAnimation
:star-count="1284"
:stargazers="stargazers"
/>
</div>
</template>
<script setup lang="ts">
const stargazers = [
{
login: 'averycole',
avatar_url: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=80&h=80&fit=crop&crop=faces',
html_url: 'https://github.com/averycole',
},
{
login: 'jordanlee',
avatar_url: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=80&h=80&fit=crop&crop=faces',
html_url: 'https://github.com/jordanlee',
},
{
login: 'samrivera',
avatar_url: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=80&h=80&fit=crop&crop=faces',
html_url: 'https://github.com/samrivera',
},
{
login: 'morganchen',
avatar_url: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=80&h=80&fit=crop&crop=faces',
html_url: 'https://github.com/morganchen',
},
{
login: 'rileyng',
avatar_url: 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=80&h=80&fit=crop&crop=faces',
html_url: 'https://github.com/rileyng',
},
]
</script>
Leave out starCount and the component asks GitHub for the repository named by
owner and repo after mount. Nothing is fetched on the server, so the markup
is identical for every visitor. Supplying starCount alone skips the request
and leaves the avatar stack empty, which is the compact form.
Customize
showAvatars drops the stack and leaves the count on its own. The count also
accepts a class through countClassName, which is handy when the widget is the
headline rather than a footnote.
<template>
<div class="flex w-full items-center justify-center p-6">
<MGitHubStarsAnimation
:show-avatars="false"
:star-count="24810"
count-class-name="text-lg"
/>
</div>
</template>
The fetch can be pointed at your own endpoint with apiEndpoint; it receives
owner and repo as query parameters and should answer with { stars, stargazers }. A failed request leaves the component empty instead of showing a
zero that is not true.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
starCount | number | - | Star total. When provided, no request is made, even if stargazers is omitted. |
stargazers | Stargazer[] | - | Avatars to show. Each entry takes login, avatar_url and html_url. Only
used as-is when starCount is also provided. |
owner | string | 'vuejs' | Repository owner used when neither data prop is supplied. |
repo | string | 'core' | Repository name used when neither data prop is supplied. |
apiEndpoint | string | - | Custom endpoint for the star data. Falls back to the GitHub API. |
maxAvatars | number | 5 | Maximum number of avatars in the stack. |
showAvatars | boolean | true | Hide the avatar stack and render only the count. |
avatarClassName | string | - | Extra classes for each avatar link. |
countClassName | string | - | Extra classes for the count row. |
class | string | - | Extra classes for the root. |
Accessibility
- Each avatar is a real link with an
aria-labelnaming the profile, plus analtof<login>'s avatar. The two never repeat silently in the same tab stop. - Links open in a new tab with
rel="noopener noreferrer", so the original page keeps its opener state. - The star icon is decorative; the count and the "star"/"stars" suffix are real text, so the number is never conveyed by an icon alone.
- Count-up is presentation only: it is not wrapped in a live region, so screen readers do not hear the same figure climbing for two seconds. They read the final value when they reach it.
- The count lands on the exact value at the end rather than a rounded one, and the plural switches at one.
- Under
prefers-reduced-motionthe counter jumps straight to the total and the avatar and count entrances are disabled. - The loading skeleton is hidden from assistive technology; there is no live announcement for a state that usually resolves in a second.
Performance
- Passing
starCountavoids the network completely. Otherwise the request starts after mount and is aborted if the component unmounts. - The countdown uses a single animation frame loop that cancels itself at the target and is cleared on unmount.
- Only the count text updates while the loop runs; the avatar list and layout do not re-render.
- Avatar and count entrances are CSS animations, and the count is laid out in tabular figures so the row does not jump as digits change.
- The avatar stack overlaps with negative margins, so it adds no measurement or wrapper per avatar.