Skip to content

Conversation

@solidusite
Copy link

@solidusite solidusite commented Dec 27, 2025

πŸ”— Linked issue

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

Summary

This PR integrates the StatCard component with optional progress bar and sparkline chart functionality, consolidating multiple stat components into a single, flexible component.

StatCard displays key metrics with an icon, title, value, and optional trend indicator. The trend direction is automatically calculated from the trend value (negative = down/red, positive = up/green) but can be explicitly overridden when needed. It can optionally display a progress bar (via current and max props) or a sparkline chart (via data prop), making it suitable for dashboard use cases.

Customization Options

The component is customizable through:

  • Variants: outline, solid, soft, subtle, ghost, naked for different visual styles
  • Sizes: xs, sm, md, lg, xl for different card dimensions
  • Colors: All theme colors (primary, success, warning, error, info, etc.) for icons and accents
  • Props: icon, title, value, trend, trendDirection, current, max, showLabel, data, strokeWidth, showArea, height
  • Slots: icon, title, value, label, progress, sparkline, trend, and default for full customization
  • Theme: All slots are themeable through the ui prop or global theme configuration

Key Features

  • Smart Trend Direction: Automatically infers trend direction from the trend value (negative = red down arrow, positive = green up arrow), with explicit override support
  • Flexible Data Display: Choose between progress bars for goal tracking or sparkline charts for trend visualization
  • Consistent Variants: Follows the same variant conventions as PageCard for visual consistency

Personal Note

This PR is part of an effort to create a comprehensive suite of components for displaying charts and statistics in dashboards. The goal is to provide a unified, flexible API that covers common dashboard use cases while maintaining consistency and ease of use.

Implementation Note

The sparkline charts are generated using native SVG (no external dependencies) to keep the bundle size minimal and ensure fast rendering. The SVG paths are calculated client-side based on the provided data array.

Feedback Requested

I'd love to get your feedback on:

  • Whether this consolidation approach makes sense, or if separate components would be preferred
  • The API design and prop naming (especially the automatic trend direction calculation)
  • Any additional features or use cases we should consider
  • The overall direction of the Stat Suite
  • The SVG-based chart approach vs. using a dedicated charting library

TODO

  • Customize the stat-card.png images in docs/public/components/light/ and docs/public/components/dark/ with actual screenshots showing the component with progress bars and sparklines

screencapture-localhost-3000-components-stat-card-2025-12-27-13_52_59

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@ineshbose
Copy link
Member

ineshbose commented Dec 27, 2025

I'd love to get your feedback on:

  • Any additional features or use cases we should consider

If OK to share my feedback, I have something like this in my app (more of a dynamic Dashboard) and I am using number-flow to animate numbers, but my concern would be how NuxtUI ships + maintains it (so I am not stressing for having it added).

ref #978 (comment)

@yeonjulee1005
Copy link
Contributor

@solidusite

It looks like a really great component! (I hope it gets adopted!)
Assuming it does, I’d like to share a couple of suggestions as well,

  1. When trendDirection is set to down, it might be better if the direction icon color is also displayed in red.
  2. Additionally, when the trend value is negative, it could be useful to calculate and assign trendDirection automatically based on that value.

@solidusite
Copy link
Author

If OK to share my feedback, I have something like this in my app (more of a dynamic Dashboard) and I am using number-flow to animate numbers, but my concern would be how NuxtUI ships + maintains it (so I am not stressing for having it added)

Thanks for the feedback! I understand what you mean, but I’d like to keep things as consistent as possible with NuxtUI’s standards (avoiding external libraries as dependencies).

@solidusite
Copy link
Author

@solidusite

It looks like a really great component! (I hope it gets adopted!) Assuming it does, I’d like to share a couple of suggestions as well,

  1. When trendDirection is set to down, it might be better if the direction icon color is also displayed in red.
  2. Additionally, when the trend value is negative, it could be useful to calculate and assign trendDirection automatically based on that value.

Thanks for the feedback! I'm glad you like the component.
Both of your suggestions have already been implemented! πŸŽ‰

The trendDirection is now automatically inferred from the trend value:
Positive values (e.g., trend={12.5}) β†’ Green up arrow ↑
Negative values (e.g., trend={-8.2}) β†’ Red down arrow ↓
You can still explicitly override this behavior when needed.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 27, 2025

npm i https://pkg.pr.new/@nuxt/ui@5758

commit: 33da07c

Copy link
Member

@benjamincanac benjamincanac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the StatGroup component really necessary? We could use PageGrid to do this. Maybe it would make sense to call this component PageStat for consistency? πŸ€”

@solidusite
Copy link
Author

Is the StatGroup component really necessary? We could use PageGrid to do this. Maybe it would make sense to call this component PageStat for consistency? πŸ€”

Yeah, I agree β€” I don’t think it’s strictly necessary, at least for now.

The original idea was to have a wrapper component that could group different kinds of elements (not just StatCard), for example if in the future we decide to introduce charts or other stats-related components.

That said, I’m fine with simplifying this. I’ll remove it and push the changes.

@solidusite solidusite force-pushed the feat/stats-components branch from a24ff45 to e559050 Compare January 5, 2026 19:21
@solidusite
Copy link
Author

solidusite commented Jan 5, 2026

@benjamincanac cleaned and rebase branch. I don't understand why deploy fail. It seems not related to my changes.

…customizable props and themes; includes documentation, examples, and tests
…out of stat cards; includes examples, documentation, and tests
…pdate documentation; add override option for explicit trend direction
…te related documentation; enhance styling for zero line visibility
… documentation, and tests; clean up theme and snapshot files
@solidusite solidusite force-pushed the feat/stats-components branch from e559050 to 2f2b118 Compare January 7, 2026 19:56
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
</slot>
</div>

<div v-if="(value !== undefined && value !== null) || (current !== undefined && current !== null && value === undefined) || !!slots.value" data-slot="value" :class="ui.value({ class: props.ui?.value })">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<div v-if="(value !== undefined && value !== null) || (current !== undefined && current !== null && value === undefined) || !!slots.value" data-slot="value" :class="ui.value({ class: props.ui?.value })">
<div v-if="(value !== undefined && value !== null) || (current !== undefined && current !== null && (value === undefined || value === null)) || !!slots.value" data-slot="value" :class="ui.value({ class: props.ui?.value })">

The value display condition inconsistently checks only for undefined in the fallback case, but the display expression checks for both undefined and null. This causes the value div to not render if value={null} and current exists, even though content should be displayed.

View Details

Analysis

StatCard value display condition inconsistently checks for undefined only in fallback case

What fails: StatCard component fails to render the value div when value={null} and current exists. The v-if condition on line 251 checks only value === undefined instead of handling both undefined and null like the display expression does.

How to reproduce:

<StatCard :value="null" :current="50" :max="100" />

Result: The value div does not render, even though the display expression would correctly show 50 as fallback.

Expected: The value div should render with the fallback value of 50 because when value={null}, it should fall back to current, just like when value={undefined}.

Inconsistency: Lines 145, 154, and 160 use the pattern === undefined || === null for null/undefined checks throughout the component, but the second condition of the v-if on line 251 only checks value === undefined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants