diff --git a/core/src/user-components/tags/bigvalue/BigValue.svelte b/core/src/user-components/tags/bigvalue/BigValue.svelte
index 390204714a..dceb01ea61 100644
--- a/core/src/user-components/tags/bigvalue/BigValue.svelte
+++ b/core/src/user-components/tags/bigvalue/BigValue.svelte
@@ -55,6 +55,7 @@
const comparison = $derived(model.resolvedComparison);
// Coerce booleans in case they came from variable interpolation as strings
const comparison_delta = $derived(coerceBoolean(comparison?.delta) ?? true);
+ const comparison_chip = $derived(coerceBoolean(comparison?.chip) ?? false);
const sparkline = $derived(model.resolvedSparkline);
const sparkline_type = $derived((sparkline?.type ?? 'line') as 'line' | 'area' | 'bar');
const sparkline_color = $derived(sparkline?.color);
@@ -81,9 +82,11 @@
);
const down_is_good = $derived(coerceBoolean(comparison?.down_is_good) ?? false);
const neutralRange = $derived(comparison?.neutral_range ?? [0, 0]);
+ const isCard = $derived(props.card ?? false);
const max_width = $derived(props.max_width ?? 'none');
const min_width = $derived(props.min_width ?? 'auto');
const title_class = $derived(props.title_class);
+ const subtitle_class = $derived(props.subtitle_class);
const value_class = $derived(props.value_class);
const text_size = $derived(props.text_size);
const comparison_class = $derived(undefined);
@@ -148,11 +151,13 @@
// Use resolved props from model (handles variable interpolation)
const resolvedTitle = $derived(model.resolvedTitle);
+ const resolvedSubtitle = $derived(model.resolvedSubtitle);
const info = $derived(model.resolvedInfo);
const info_link = $derived(model.resolvedInfo_link);
const info_link_title = $derived(model.resolvedInfo_link_title);
const title = $derived(resolvedTitle || (props.value ? valueProcessed.displayAlias : ''));
+ const subtitle = $derived(resolvedSubtitle || '');
// Extract data points from the single-row query results
const resultRow = $derived.by(() => {
@@ -280,7 +285,13 @@
{/if}
+ {#if subtitle}
+
+ {subtitle}
+
+ {/if}
diff --git a/core/src/user-components/tags/bigvalue/BigValueModel.svelte.ts b/core/src/user-components/tags/bigvalue/BigValueModel.svelte.ts
index ee208dcd12..371c6eae38 100644
--- a/core/src/user-components/tags/bigvalue/BigValueModel.svelte.ts
+++ b/core/src/user-components/tags/bigvalue/BigValueModel.svelte.ts
@@ -59,6 +59,7 @@ export class BigValueModel extends UserComponentModel {
if (this.metricCompiled) return this.metricCompiled.displayLabel;
return explicit;
});
+ readonly resolvedSubtitle = $derived(this.resolveText(this.attributes.subtitle));
readonly resolvedInfo = $derived(this.resolveText(this.attributes.info));
readonly resolvedInfo_link = $derived(this.resolveText(this.attributes.info_link));
readonly resolvedInfo_link_title = $derived(this.resolveText(this.attributes.info_link_title));
diff --git a/core/src/user-components/tags/bigvalue/bigvalue.test.ts b/core/src/user-components/tags/bigvalue/bigvalue.test.ts
index 12c77c961e..a7583b636a 100644
--- a/core/src/user-components/tags/bigvalue/bigvalue.test.ts
+++ b/core/src/user-components/tags/bigvalue/bigvalue.test.ts
@@ -1,4 +1,5 @@
import { describe, it, expect } from 'vitest';
+import { schema } from './schema';
import { buildBigValueSQL, type BigValueSQLAttrs } from './build-bigvalue-sql';
import { orderCompatibleWithSingleValue } from '../../validators';
import {
@@ -602,3 +603,12 @@ describe('order × value compatibility (author-time guard for the GROUP BY ALL s
).toEqual([]);
});
});
+
+describe('big_value schema attributes', () => {
+ it('supports subtitle, card, and comparison.chip', () => {
+ expect(schema.attributes.title).toBeDefined();
+ expect(schema.attributes.subtitle).toBeDefined();
+ expect(schema.attributes.card).toBeDefined();
+ expect(schema.attributes.comparison).toBeDefined();
+ });
+});
diff --git a/core/src/user-components/tags/bigvalue/schema.ts b/core/src/user-components/tags/bigvalue/schema.ts
index 64cbf4110c..337e899301 100644
--- a/core/src/user-components/tags/bigvalue/schema.ts
+++ b/core/src/user-components/tags/bigvalue/schema.ts
@@ -63,6 +63,13 @@ const comparisonSchema = baseComparisonSchema
'Range [min, max] for neutral values. Use null for infinity (e.g., [null, 0] means anything ≤ 0 is neutral)'
),
{ supportsVariables: true }
+ ),
+ chip: setZodMetadata(
+ booleanVariableSchema
+ .optional()
+ .default(false)
+ .describe('Whether to display the comparison delta as a chip / pill badge'),
+ { supportsVariables: true }
)
})
.optional();
@@ -205,6 +212,21 @@ const attributes = {
supportsVariables: true,
variableContext: 'text'
},
+ subtitle: {
+ type: String,
+ required: false,
+ description: 'Subtitle displayed below the title',
+ affectsQuery: false,
+ supportsVariables: true,
+ variableContext: 'text'
+ },
+ card: {
+ type: Boolean,
+ required: false,
+ default: false,
+ description: 'Whether to style the component as an individual card with border and padding',
+ affectsQuery: false
+ },
max_width: {
type: String,
required: false,
@@ -233,6 +255,12 @@ const attributes = {
description: 'Additional CSS classes for the title',
affectsQuery: false
},
+ subtitle_class: {
+ type: String,
+ required: false,
+ description: 'Additional CSS classes for the subtitle',
+ affectsQuery: false
+ },
value_class: {
type: String,
required: false,