Skip to content
How-to guides and worked examplesSee the guides β†’

Cards API ​

Per-option reference for cards. For task-oriented documentation and worked examples, see the Cards guide.

Options are class attributes set on the card class. Unless noted, any option can also be a Proc β€” it is evaluated through Avo::ExecutionContext, where you gain access to all its attributes plus parent, resource, dashboard, card, arguments, and params.

ruby
# app/avo/cards/users_metric.rb
class Avo::Cards::UsersMetric < Avo::Cards::MetricCard
  self.id = "users_metric"
  self.label = "Users count"
  # options listed below
end

Base settings ​

Available on every card type.

self.id

The card's unique identifier. Used to build the card's Turbo frame and paths, so it must be unique across the parent.

ruby
self.id = "users_metric"
  • Type: String
  • Default: nil
  • Required: yes

self.label

The card's title, rendered at the top of the card.

ruby
self.label = "Users count"
  • Type: String or Proc
  • Default: nil

self.description

Renders directly under the title as a subtitle. Use it for context that should always be visible at a glance.

ruby
self.description = "Across all teams and workspaces"
  • Type: String or Proc
  • Default: nil

self.discreet_description

Renders a small info icon in the bottom-right corner of the card; hovering it shows the text as a tooltip. Use it for secondary context β€” methodology notes, data-source disclaimers, definitions.

ruby
self.discreet_description = "Counts only active, non-deleted users."
  • Type: String or Proc
  • Default: nil

self.cols

How many columns of the parent's grid the card spans.

ruby
self.cols = 2
  • Type: Integer (or Proc returning one)
  • Default: 1
  • Values: 1 to 6

self.rows

How many rows of the parent's grid the card spans. On table and list cards it also caps the card's height β€” rows past the cap scroll inside the card.

ruby
self.rows = 2
  • Type: Integer (or Proc returning one)
  • Default: 1
  • Values: 1 to 12

self.display_header

Whether the card header (label and ranges dropdown) is rendered. Set it to false to let embedded content β€” a map, an iframe β€” fill the whole card flush to its edges.

ruby
self.display_header = false

As a Proc it has access to card, parent, dashboard (nil when the parent is a resource), and resource (nil when the parent is a dashboard).

  • Type: Boolean (or Proc returning one)
  • Default: true

self.visible

Controls whether the card renders. As a Proc it has access to context, params, parent, dashboard (nil when the parent is a resource), resource (nil when the parent is a dashboard), and card.

ruby
self.visible = -> { current_user.admin? }
  • Type: Boolean or Proc
  • Default: true

self.refresh_every

Auto-refreshes the card on an interval. Pass a duration; Avo reloads the card in the background.

ruby
self.refresh_every = 10.minutes
  • Type: ActiveSupport::Duration (or seconds as an Integer)
  • Default: nil

WARNING

On table/list cards a refresh reloads the whole card, resetting the scroll position of a tall table. Prefer it on short cards.

INFO

This controls the interval only. A card can also carry a manual refresh control β€” see self.refresh_button. Refreshing by hand restarts this countdown.

self.refresh_button

Renders a manual refresh control on the card, so a viewer can bring that one card up to date without reloading the page.

ruby
self.refresh_button = true
  • Type: Boolean
  • Default: false

The control sits at the end of the card header, or in the card's top corner when it renders no header. See Refresh a card on demand for how it behaves.

INFO

Same name and same default as the dashboard's refresh_button, which refreshes every card at once. Setting it on a dashboard does not turn it on for that dashboard's cards β€” the two are independent.

self.cache_for

Caches the result of the card's query for that duration. Within the window the query is skipped entirely and the stored result is replayed β€” useful for cards whose query is expensive.

ruby
class Avo::Cards::UsersCount < Avo::Cards::MetricCard
  self.cache_for = 5.minutes

  def query
    result User.where(active: true).count
  end
end

Avo caches through Avo.configuration.cache_store. The key is scoped to the current user and tenant, so a card querying current_user never serves one user's data to another. In full, it covers:

PartWhy
Card class, parent, and positionSeparates cards, and two registrations of the same class
range and the dashboard's global rangeEach range is its own result
Current user and tenantKeeps per-user and per-tenant queries apart
The resource's view and recordA resource card caches per record and per view
  • Type: ActiveSupport::Duration (or seconds as an Integer), or a Proc returning one
  • Default: nil (no caching)

INFO

The card's refresh control bypasses the cache: clicking it re-runs the query and rewrites the entry, so the card never animates over a stale value. Because the key is per user, that only busts the clicker's own entry. self.refresh_every polling does not bypass it β€” pair the two to poll a card often while querying rarely.

WARNING

Cards with no query β€” HTML and partial cards β€” build their content at render time, so cache_for does nothing for them. Wrap the markup in Rails' own cache block instead.

INFO

arguments is deliberately not part of the key. It's fixed at registration time, so a card's position already separates two registrations of the same class.

If your query reads something the key doesn't cover, override cache_key:

ruby
def cache_key
  super + [Current.account.id]
end

Returning a narrower key is how you opt into sharing one entry across users.

WARNING

Outside production Avo.configuration.cache_store defaults to a file store under tmp/cache, which isn't shared between machines β€” on a multi-server staging environment each server caches on its own. Set config.cache_store in the Avo initializer to share it.

Ranges ​

Let the user query data across different time ranges via a dropdown in the card header.

self.ranges

The options shown in the range dropdown. The value is passed straight to Rails' options_for_select, so it behaves like a select_tag. Integers are treated as a number of days; other strings ("TODAY", "MTD", "ALL", …) are passed through as-is and it's up to your query to interpret them.

ruby
self.ranges = {
  "7 days": 7,
  "30 days": 30,
  "Year to date": "YTD",
  All: "ALL"
}
  • Type: Array or Hash
  • Default: []

self.initial_range

The range selected by default when the card first loads. Falls back to the first entry of ranges.

ruby
self.initial_range = 30
  • Type: matches a ranges value
  • Default: nil

Metric card ​

Avo::Cards::MetricCard displays a single big number returned from query/result.

self.prefix

Text rendered before the value (e.g. a currency symbol).

ruby
self.prefix = "$"
  • Type: String or Proc
  • Default: nil

self.suffix

Text rendered after the value (e.g. %).

ruby
self.suffix = "%"
  • Type: String or Proc
  • Default: nil

self.format

Formats the value returned by result before display. The block runs through Avo::ExecutionContext with value (the raw result) in scope and Rails' NumberHelper plus Avo::ApplicationHelper mixed in, so helpers like number_to_currency and number_to_social are available directly.

ruby
self.format = -> { number_to_social value, start_at: 1_000 }
  • Type: Proc
  • Default: -> { number_to_social value.to_i, start_at: 10_000 }

Chartkick card ​

Avo::Cards::ChartkickCard renders a chart via the chartkick gem, which you must add to your Gemfile.

self.chart_type

The kind of chart to render.

ruby
self.chart_type = :area_chart
  • Type: Symbol
  • Default: nil
  • Values: :line_chart, :pie_chart, :column_chart, :bar_chart, :area_chart, :scatter_chart

self.chart_options

Extra chartkick options merged on top of Avo's defaults β€” use it for anything not covered by flush, legend, and friends. As a Proc it has access to parent, arguments, and result_data.

ruby
self.chart_options = {
  library: { plugins: { legend: { display: true } } }
}
  • Type: Hash or Proc returning a Hash
  • Default: {}

INFO

The class attribute is chart_options. chartkick_options is the internal, read-only method that merges your chart_options into Avo's defaults β€” you don't set it.

self.flush

Offsets chartkick's built-in padding so the chart sits flush inside the card. Set it to false to render the chart with its default padding and unlock scale, legend, legend_on_left, and legend_on_right.

ruby
self.flush = false
  • Type: Boolean
  • Default: true

self.legend

Shows the chart legend. Takes effect once flush is false.

ruby
self.legend = true
  • Type: Boolean
  • Default: false

self.scale

Shows the chart's axis scales. Takes effect once flush is false.

ruby
self.scale = true
  • Type: Boolean
  • Default: false

self.legend_on_left

Positions the legend on the left. Takes effect once flush is false.

ruby
self.legend_on_left = true
  • Type: Boolean
  • Default: false

self.legend_on_right

Positions the legend on the right. Takes effect once flush is false.

ruby
self.legend_on_right = true
  • Type: Boolean
  • Default: false

Partial card ​

Avo::Cards::PartialCard renders a custom partial.

self.partial

Path to the partial that renders the card's body.

ruby
self.partial = "avo/cards/map_card"
  • Type: String
  • Default: nil

HTML card ​

Since v4.1

Avo::Cards::HtmlCard builds its body from Ruby instead of a partial file. It has no extra class attributes β€” you implement a body method.

body

Instance method returning the card's content. Every view helper (tag, safe_join, link_to, number_to_currency, render, main_app, …) is available directly on the card. The return value is resolved by shape:

Return valueRendered as
ActiveSupport::SafeBuffer (tag helpers, explicit render)passed through untouched
Stringcompiled as an inline ERB template, with card as a local
anything else (a component instance, a {partial:} hash)passed to render
ruby
def body
  tag.div class: "px-4 py-2" do
    tag.strong(number_to_currency(149.99))
  end
end
  • Type: instance method
  • Required: yes β€” raises NotImplementedError if undefined

WARNING

Inline template strings go through ActionView, so interpolated ERB values are HTML-escaped as usual. Never build the template string itself from user input β€” pass dynamic values through ERB tags or locals:, not Ruby string interpolation.

Data cards (table & list) ​

Since v4.1

Avo::Cards::TableCard and Avo::Cards::ListCard both inherit from Avo::Cards::DataCard: you declare columns with fields and return records from query. The table card renders a <table> with column headers; the list card renders a <ul> with no headers, the first field as each row's primary content and the rest trailing at the end edge.

The records you return must have an Avo resource registered for their model β€” cells render through it using the same components as the resource <Index /> view.

self.fields

Instance method declaring the card's columns, using the same field DSL as resources. Each field becomes one cell per row; every field type and option (format_using, link_to_record, badge options:, computed blocks, …) behaves as it does on an index table.

ruby
def fields
  field :name, as: :text, name: "User", link_to_record: true
  field :active, as: :badge, options: {success: "Active"} do
    record.active? ? "Active" : "Inactive"
  end
end
  • Type: instance method
  • Note: an invalid field configuration raises ArgumentError

self.query

Instance method returning the records, wrapped in result. There is no pagination or sorting β€” cap the row count with limit. The selected range is available as range.

ruby
def query
  result User.order(created_at: :desc).limit(10)
end
  • Type: instance method

self.row_url

When set, every row becomes a link. The block runs per row with record in scope and returns either a URL string or a Hash β€” following the same semantics as discreet information.

ruby
self.row_url = -> {
  {url: record_path(record), target: :_blank, tooltip: "View #{record.name}"}
}
  • Type: Proc
  • Default: nil
  • Returns: a URL String, or {url:, target:, tooltip:} (each value may itself be a Proc receiving record)
  • Values: url accepts http, https, mailto, and relative URLs; anything else (e.g. javascript:) is ignored

self.density

Vertical spacing of the rows.

ruby
self.density = :tight
  • Type: Symbol (or Proc returning one)
  • Default: the global config.density, or :normal
  • Values: :tight, :normal, :relaxed

self.empty_message

Message shown when query returns no rows.

ruby
self.empty_message = "No sign-ups this week"
  • Type: String or Proc
  • Default: the translated default
  • i18n key: avo.no_item_found ("No record found")

Registration overrides ​

When you register a card on a parent you can override its settings inline, without editing the card class. This is how you reuse one card class with different labels, ranges, or queries.

card

Registers a card on a dashboard or resource. Every keyword overrides the card's own attribute for that registration.

ruby
def cards
  card Avo::Cards::UsersCount,
    label: "Active users",
    description: "Active users count",
    cols: 2,
    rows: 2,
    visible: -> { true },
    refresh_every: 2.minutes,
    cache_for: 5.minutes,
    chart_options: {library: {plugins: {legend: {display: true}}}},
    arguments: {active_users: true}
end
  • Overridable keys: label, description, discreet_description, cols, rows, refresh_every, cache_for, visible, chart_options, arguments

arguments

Arbitrary data forwarded from the registration to the card, readable as arguments inside the card's methods. Use it to parameterize one card class instead of duplicating it.

ruby
# on the parent
card Avo::Cards::UsersCount, arguments: {active_users: true}

# in the card
def query
  scope = User
  scope = scope.active if arguments[:active_users].present?
  result scope.count
end
  • Type: Hash
  • Default: {}

Dividers ​

Separate cards with a divider, declared in the cards method.

divider

Adds a divider between cards. With a label it shows text; with invisible: true it adds spacing but draws no line or label.

ruby
def cards
  card Avo::Cards::ExampleColumnChart
  divider label: "Custom partials"
  card Avo::Cards::MapCard
end
  • label: String β€” text shown on the divider. Default nil.
  • invisible: Boolean β€” when true, renders no border or label. Default false.
  • visible: Boolean or Proc β€” conditionally show the divider. Default true. As a Proc it has access to context, params, parent, dashboard, and resource.