Senren UI
Components / Stat Card

Stat Card

Stable

dashboard KPI tiles

MRR

$24.8k

+12% this month

Users

8,412

+4% this week

Churn

1.8%

-0.3% this month

Usage example #

Copy this ERB into a Rails view after installing the component. The snippet below is the same code used by the live preview above.

app/views/.../stat_card_example.html.erb
<div class="grid w-full max-w-xl gap-3 sm:grid-cols-3">
  <% [
    ["MRR", "$24.8k", "+12%", "this month", :success],
    ["Users", "8,412", "+4%", "this week", :default],
    ["Churn", "1.8%", "-0.3%", "this month", :warning]
  ].each do |label, value, change, helper_text, variant| %>
    <%= render Senren::StatCardComponent.new(label: label, value: value, change: change, helper_text: helper_text, variant: variant) %>
  <% end %>
</div>

Install this component #

Copy the official component into your app

Use this when you want the Senren-maintained implementation copied into app/components/senren.

Terminal
bin/rails senren:add stat_card

Create a custom component with the same conventions

Use this when you need an app-specific static component that follows Senren's ViewComponent structure.

Terminal
bin/rails generate senren:component stat_card --no-client

Dependencies are resolved by senren:add: card, typography.

At a glance #

Category Saas
Class name Senren::StatCardComponent
Stimulus
Variants default, success, warning, destructive
Depends on card, typography
Pairs with badge

Source #

app/components/senren/stat_card_component.html.erb
<%= tag.div(**root_attrs("rounded-(--senren-radius) border bg-[hsl(var(--senren-card))] p-5 shadow-sm")) do %>
  <% if content? && label.blank? && value.blank? %>
    <%= content %>
  <% else %>
    <% if label.present? %>
      <p class="text-xs font-semibold uppercase tracking-wide text-[hsl(var(--senren-muted-foreground))]"><%= label %></p>
    <% end %>
    <% if value.present? %>
      <p class="mt-2 font-display text-3xl font-semibold tracking-tight text-[hsl(var(--senren-foreground))]"><%= value %></p>
    <% end %>
    <div class="mt-3 flex items-center justify-between gap-3 text-sm">
      <% if change.present? %>
        <span class="font-medium <%= accent_class %>"><%= change %></span>
      <% end %>
      <% if helper_text.present? %>
        <span class="text-[hsl(var(--senren-muted-foreground))]"><%= helper_text %></span>
      <% end %>
    </div>
  <% end %>
<% end %>
app/components/senren/stat_card_component.rb
# frozen_string_literal: true

module Senren
  class StatCardComponent < BaseComponent
    VARIANTS = {
      default: 'border-[hsl(var(--senren-border))]',
      success: 'border-[hsl(var(--senren-success)/0.4)]',
      warning: 'border-[hsl(var(--senren-warning)/0.5)]',
      destructive: 'border-[hsl(var(--senren-destructive)/0.42)]'
    }.freeze
    SIZES = { md: '' }.freeze

    def initialize(label: nil, value: nil, change: nil, helper_text: nil, variant: :default, class_name: nil, **html)
      super(variant: variant, size: :md, class_name: class_name, **html)
      @label = label
      @value = value
      @change = change
      @helper_text = helper_text
    end

    attr_reader :label, :value, :change, :helper_text

    def accent_class
      {
        success: 'text-[hsl(var(--senren-success))]',
        warning: 'text-[hsl(var(--senren-warning))]',
        destructive: 'text-[hsl(var(--senren-destructive))]'
      }.fetch(variant, 'text-[hsl(var(--senren-muted-foreground))]')
    end
  end
end

AI agent rules #

Use for

  • +dashboard KPI tiles

Avoid

  • -interactive widgets - use card with explicit controls

Accessibility #

  • Avoid color-only meaning; include label and value text.