A powerful and flexible statistics display block for showcasing key metrics, achievements, or numerical data with smooth animations and customizable styling.
Install the Stats Counter block via our CLI tool:
npx ngsui-cli add block stats-counter- π― Multiple Variants: 7 component variants and 7 stat variants for maximum flexibility
- π¬ Smooth Animations: Built-in scroll-triggered animations with customizable timing
- π Trend Indicators: Built-in support for trend arrows and percentage changes
- π¨ Customizable Styling: Full theming support with CVA-based variants
- π± Responsive Design: Mobile-first responsive layout
- π§ Content Projection: Header and footer slots for additional content
- βΏ Accessibility: ARIA-compliant with screen reader support
- π Dark Mode: Full dark mode support
import { StatsCounter } from '@ngsui/blocks';
@Component({
selector: 'app-example',
template: `
<StatsCounter
[stats]="basicStats"
variant="default">
</StatsCounter>
`,
imports: [StatsCounter]
})
export class ExampleComponent {
basicStats = [
{ value: 1200, label: 'Active Users', icon: 'π₯' },
{ value: 98, label: 'Success Rate', suffix: '%', icon: 'β
' },
{ value: 24, label: 'Countries', suffix: '+', icon: 'π' }
];
}<StatsCounter
[stats]="animatedStats"
variant="card"
[enableAnimation]="true"
[animationDuration]="2000">
</StatsCounter>@Component({
template: `
<StatsCounter
[stats]="trendStats"
variant="gradient"
[enableAnimation]="true"
statVariant="highlighted">
</StatsCounter>
`
})
export class AdvancedExampleComponent {
trendStats = [
{
value: 45678,
label: 'Monthly Revenue',
prefix: '$',
icon: 'π°',
trend: { value: 12.5, type: 'increase' as const },
description: 'vs last month'
},
{
value: 2847,
label: 'New Customers',
icon: 'π₯',
trend: { value: 8.2, type: 'increase' as const },
description: 'this quarter'
},
{
value: 99.9,
label: 'Uptime',
suffix: '%',
icon: 'β‘',
trend: { value: 0.1, type: 'increase' as const },
description: 'SLA compliance'
}
];
}
value: 2500,
label: 'Projects Completed',
suffix: '+',
trend: { direction: 'up', value: 8 }
}
// ... more stats
];
}| Property | Type | Default | Description |
|---|---|---|---|
stats |
StatItem[] |
[] |
Array of statistics to display |
title |
string |
'' |
Main header title |
description |
string |
'' |
Header description text |
badge |
string |
'' |
Badge text above title |
summaryText |
string |
'' |
Footer summary message |
| Property | Type | Default | Description |
|---|---|---|---|
columns |
number |
4 |
Number of columns (1-6) |
gap |
'sm' | 'default' | 'lg' |
'default' |
Grid gap size |
| Property | Type | Default | Description |
|---|---|---|---|
showHeader |
boolean |
true |
Show header section |
showFooter |
boolean |
false |
Show footer section |
showIcons |
boolean |
true |
Display stat icons |
showDescriptions |
boolean |
true |
Show stat descriptions |
showTrends |
boolean |
true |
Display trend indicators |
showCustomFields |
boolean |
false |
Show custom properties |
showSummary |
boolean |
false |
Display summary section |
| Property | Type | Default | Description |
|---|---|---|---|
enableAnimation |
boolean |
true |
Enable count-up animations |
animationDuration |
number |
2000 |
Animation duration (ms) |
animationDelay |
number |
100 |
Delay between stat animations |
triggerOnScroll |
boolean |
true |
Animate when scrolled into view |
| Property | Type | Default | Description |
|---|---|---|---|
variant |
StatsCounterVariant |
'default' |
Container visual style |
statVariant |
StatItemVariant |
'default' |
Individual stat style |
statLayout |
'vertical' | 'horizontal' | 'overlay' |
'vertical' |
Stat item layout |
| Event | Type | Description |
|---|---|---|
statClick |
{stat: StatItem, index: number} |
Stat item clicked |
statHover |
{stat: StatItem, index: number} |
Stat item hovered |
animationComplete |
StatItem[] |
All animations completed |
interface StatItem {
id?: string | number;
value: number; // The numeric value to display
label: string; // Primary label
description?: string; // Optional description
icon?: string; // HTML string for icon
prefix?: string; // Text before value (e.g., "$")
suffix?: string; // Text after value (e.g., "%", "+")
color?: string; // Custom color for value
trend?: { // Trend indicator
direction: 'up' | 'down' | 'neutral';
value: number;
period?: string;
};
formatter?: (value: number) => string; // Custom value formatter
animationDelay?: number; // Custom animation delay
customFields?: { // Additional properties
[key: string]: any;
};
}Standard layout with subtle shadows and borders.
<StatsCounter variant="default" [stats]="stats" />Clean, borderless design with minimal spacing.
<StatsCounter variant="minimal" [stats]="stats" />Elevated card design with enhanced shadows and padding.
<StatsCounter variant="card" [stats]="stats" />Prominent border with no background fill.
<StatsCounter variant="outlined" [stats]="stats" />Subtle background fill with light border.
<StatsCounter variant="filled" [stats]="stats" />Gradient background from light to dark.
<StatsCounter variant="gradient" [stats]="stats" />Glassmorphism effect with backdrop blur.
<StatsCounter variant="glass" [stats]="stats" />Large, prominent design for hero sections.
<StatsCounter variant="hero" [stats]="stats" />Standard stat card with border and shadow.
Clean stat without borders or background.
Enhanced stat card with hover animations.
Stat with prominent border, no fill.
Stat with background fill and border.
Glassmorphism stat item effect.
Gradient background stat item.
Icon-focused layout with minimal borders.
export class AdvancedStatsComponent {
salesStats: StatItem[] = [
{
value: 2500000,
label: 'Total Revenue',
prefix: '$',
formatter: (value) => `${(value / 1000000).toFixed(1)}M`,
trend: { direction: 'up', value: 18, period: 'vs last quarter' },
animationDelay: 0
},
{
value: 450000,
label: 'Monthly Recurring Revenue',
prefix: '$',
formatter: (value) => `${(value / 1000).toFixed(0)}K`,
trend: { direction: 'up', value: 12, period: 'MoM' },
animationDelay: 200
}
];
}<StatsCounter
[stats]="salesStats"
title="Sales Performance"
[enableAnimation]="true"
[animationDuration]="3000"
variant="card"
statVariant="gradient"
/>export class IconStatsComponent {
performanceStats: StatItem[] = [
{
value: 2500000,
label: 'Downloads',
icon: `<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>`,
color: '#3B82F6'
}
];
}export class InteractiveStatsComponent {
onStatClick(event: {stat: StatItem, index: number}): void {
// Navigate to detailed view
this.router.navigate(['/stats', event.stat.id]);
}
}<StatsCounter
[stats]="stats"
[clickable]="true"
(statClick)="onStatClick($event)"
/>export class DetailedStatsComponent {
analyticsStats: StatItem[] = [
{
value: 95.8,
label: 'Performance Score',
suffix: '%',
customFields: {
lastUpdated: '2024-01-15',
dataSource: 'Real-time Analytics',
accuracy: '99.2%',
refreshRate: '5 minutes'
}
}
];
}<StatsCounter
[stats]="analyticsStats"
[showCustomFields]="true"
/><StatsCounter [stats]="stats" [showHeader]="true" [showFooter]="true">
<!-- Custom Header -->
<div slot="header" class="text-center space-y-4">
<div class="inline-flex items-center px-4 py-2 bg-blue-100 text-blue-700 rounded-full">
π Live Dashboard
</div>
<h2 class="text-4xl font-bold">Real-time Performance</h2>
<p class="text-xl text-muted-foreground">
Monitor your KPIs with live updates
</p>
</div>
<!-- Custom Footer -->
<div slot="footer" class="text-center space-y-6">
<div class="flex justify-center space-x-4">
<button class="btn btn-primary">View Report</button>
<button class="btn btn-outline">Export Data</button>
</div>
<p class="text-sm text-muted-foreground">
Last updated: {{ lastUpdated }}
</p>
</div>
</StatsCounter><StatsCounter
[stats]="financialStats"
[columns]="2"
statLayout="horizontal"
variant="outlined"
statVariant="filled"
/>The Stats Counter automatically adapts to different screen sizes:
- Mobile (< 768px): Single column layout
- Tablet (768px - 1024px): 2-column layout (max)
- Desktop (1024px - 1280px): Up to 3-4 columns
- Large (> 1280px): Full column count as specified
.stats-counter {
--stats-border-radius: 0.75rem;
--stats-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
--stats-hover-transform: translateY(-2px);
--stats-animation-duration: 2s;
--stats-animation-timing: cubic-bezier(0.4, 0, 0.2, 1);
}// Currency formatter
const currencyFormatter = (value: number): string => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(value);
};
// Percentage formatter
const percentFormatter = (value: number): string => {
return `${value.toFixed(1)}%`;
};
// Large number formatter
const largeNumberFormatter = (value: number): string => {
if (value >= 1000000000) {
return `${(value / 1000000000).toFixed(1)}B`;
} else if (value >= 1000000) {
return `${(value / 1000000).toFixed(1)}M`;
} else if (value >= 1000) {
return `${(value / 1000).toFixed(1)}K`;
}
return value.toString();
};
// Usage
const stats: StatItem[] = [
{
value: 2500000,
label: 'Revenue',
formatter: currencyFormatter
},
{
value: 98.5,
label: 'Success Rate',
formatter: percentFormatter
},
{
value: 1500000,
label: 'Users',
formatter: largeNumberFormatter
}
];The Stats Counter includes built-in accessibility features:
- ARIA Labels: Proper labeling for screen readers
- Keyboard Navigation: Tab through interactive elements
- High Contrast: Supports system color preferences
- Reduced Motion: Respects
prefers-reduced-motion - Semantic HTML: Proper heading hierarchy and structure
<StatsCounter
[stats]="stats"
[attr.aria-label]="'Company performance statistics'"
role="region"
/>- OnPush Change Detection: Efficient rendering
- Intersection Observer: Animate only when visible
- RequestAnimationFrame: Smooth 60fps animations
- Lazy Loading: Optional for large datasets
- Limit Stat Count: Keep to 6-8 stats maximum for optimal UX
- Use Meaningful Delays: Stagger animations (100-200ms apart)
- Optimize Icons: Use SVG for crisp, scalable icons
- Cache Formatters: Create formatter functions once, reuse
- Progressive Enhancement: Provide fallbacks for reduced motion
- Modern Browsers: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
- CSS Features: CSS Grid, Flexbox, Custom Properties
- JavaScript: ES2020, Intersection Observer, RequestAnimationFrame
- Graceful Degradation: Fallbacks for older browsers
// v1.x (deprecated)
<StatsCounter
[data]="stats" // β Changed to [stats]
[showAnimation]="true" // β Changed to [enableAnimation]
[style]="'card'" // β Changed to [variant]
/>
// v2.x (current)
<StatsCounter
[stats]="stats" // β
New property name
[enableAnimation]="true" // β
Clearer naming
[variant]="'card'" // β
Consistent with design system
/>| v1.x | v2.x | Notes |
|---|---|---|
data |
stats |
More descriptive name |
showAnimation |
enableAnimation |
Clearer intent |
style |
variant |
Consistent with other components |
layout |
statLayout |
More specific scope |
Find more examples and use cases in our GitHub repository.
We welcome contributions! Please see our Contributing Guide for details.
MIT License - see LICENSE for details.