Widget
Display a figure, a chart, or both, with the context that reads them.
A widget composes a context around a stat, a chart, or both. The context is the widget’s own data: a title, a description, a footnote that reads the data, and a caption. It draws wherever the widget draws, alone or in a group.
A widget needs a stat, a chart, or both. The SDK refuses a widget with neither.
API
widget(): WidgetBuilder
| Method | Input | Result |
|---|---|---|
setTitle |
string, { icon?: string } |
Names the widget, with an optional icon. |
setDescription |
string |
Adds a neutral line under the title. |
setStat |
StatInput |
Sets the figure. Replaces the stat. |
setChart |
Nodeable<Chart> |
Sets the chart. Replaces the chart. |
setFootnote |
string, { icon?: string; tone?: Tone } |
Adds the reading of the data, with a tone. |
setCaption |
string |
Adds a muted line under the footnote. |
Properties that you do not set are not included in the protocol output.
Basic widget
A stat is a number and the number field that formats it. The renderer formats the value in the client’s locale, so the same widget reads correctly for every user.
import {
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
orders.view('overview', () =>
widget()
.setTitle('Total revenue')
.setStat({ value: 48_120, field: revenue })
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Total revenue",
"stat": {
"value": 48120,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"delta": "absolute"
}
}
]
}
}
}The stat
setStat takes an object that mirrors the stat on the wire.
| Property | Input | Result |
|---|---|---|
value |
number |
The figure. Required. |
field |
NumberFieldBuilder |
Formats the value, the prior, and the change. Required. |
prior |
number |
The value of the prior period. |
delta |
"percent" | "absolute" |
How the change from the prior is stated. Default absolute. |
tone |
"neutral" | "info" | "success" | "warning" | "danger" |
The meaning of the figure. |
description |
string |
A line under the value that reads the figure. |
descriptionIcon |
string |
An icon name beside the description. |
A currency field must carry its currency code. A stat has no row to read a per-record currency from.
Change from a prior
When you set a prior, the renderer computes the change and draws it beside the
value. percent states the change as a percent of the prior. absolute states
the difference through the field, which on a percent field is points. A prior of
zero draws no change. The sign of a change is not a meaning, so you set the tone.
import {
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const count = fields.number('count', { format: 'integer' })
const rate = fields.number('rate', {
format: 'percent',
decimals: 1,
})
orders.view('overview', () => [
widget().setTitle('Subscribers').setStat({
value: 12_480,
field: count,
prior: 11_900,
delta: 'percent',
tone: 'success',
}),
widget().setTitle('Open rate').setStat({
value: 0.42,
field: rate,
prior: 0.4,
delta: 'absolute',
tone: 'success',
}),
])
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Subscribers",
"stat": {
"value": 12480,
"field": {
"kind": "number",
"name": "count",
"format": "integer",
"label": ""
},
"prior": 11900,
"delta": "percent",
"tone": "success"
}
},
{
"kind": "widget",
"title": "Open rate",
"stat": {
"value": 0.42,
"field": {
"kind": "number",
"name": "rate",
"format": "percent",
"decimals": 1,
"label": ""
},
"prior": 0.4,
"delta": "absolute",
"tone": "success"
}
}
]
}
}
}Tone
Use a tone to give semantic meaning to the figure. The renderer controls the color and other visual details.
import {
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
orders.view('overview', () =>
widget().setTitle('Net revenue').setStat({
value: 45_700,
field: revenue,
tone: 'success',
})
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Net revenue",
"stat": {
"value": 45700,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"delta": "absolute",
"tone": "success"
}
}
]
}
}
}Description
Add a description to explain the value or compare it with another period.
import {
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const count = fields.number('count', { format: 'integer' })
orders.view('overview', () =>
widget().setTitle('Orders').setStat({
value: 1_204,
field: count,
description: '12% more than last month',
})
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Orders",
"stat": {
"value": 1204,
"field": {
"kind": "number",
"name": "count",
"format": "integer",
"label": ""
},
"delta": "absolute",
"description": "12% more than last month"
}
}
]
}
}
}The description can include a separate icon name.
import {
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const count = fields.number('count', { format: 'integer' })
orders.view('overview', () =>
widget().setTitle('Orders').setStat({
value: 1_204,
field: count,
description: '12% more than last month',
descriptionIcon: 'trending-up',
})
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Orders",
"stat": {
"value": 1204,
"field": {
"kind": "number",
"name": "count",
"format": "integer",
"label": ""
},
"delta": "absolute",
"description": "12% more than last month",
"descriptionIcon": "trending-up"
}
}
]
}
}
}The chart
setChart takes a chart builder from charts. A chart draws inside the widget,
under the stat when both are set.
Spark area
A spark area chart shows a trend without axes or tooltips. Pass records to
charts.sparkArea(data). Use setFields to select x and one or more number
fields in series.
import {
charts,
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
orders.view('overview', () =>
widget()
.setTitle('Total revenue')
.setStat({ value: 48_120, field: revenue })
.setChart(
charts
.sparkArea([
{ day: 'Mon', revenue: 12 },
{ day: 'Tue', revenue: 18 },
{ day: 'Wed', revenue: 15 },
{ day: 'Thu', revenue: 22 },
{ day: 'Fri', revenue: 30 },
])
.setFields({
x: fields.text('day'),
series: revenue,
})
)
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Total revenue",
"stat": {
"value": 48120,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"delta": "absolute"
},
"chart": {
"kind": "spark-area",
"fields": {
"x": {
"kind": "text",
"name": "day",
"label": ""
},
"series": [
{
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
}
]
},
"data": [
{
"day": "Mon",
"revenue": 12
},
{
"day": "Tue",
"revenue": 18
},
{
"day": "Wed",
"revenue": 15
},
{
"day": "Thu",
"revenue": 22
},
{
"day": "Fri",
"revenue": 30
}
]
}
}
]
}
}
}Bar list
A bar list ranks rows by one number field. Use setFields({ label, value })
to select the label field and the number field.
import {
charts,
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const sources = [
{ source: 'Search', count: 412 },
{ source: 'Referral', count: 231 },
{ source: 'Direct', count: 128 },
]
orders.view('overview', () =>
widget()
.setTitle('Orders by source')
.setChart(
charts.barList(sources).setFields({
label: fields.text('source', { label: 'Source' }),
value: fields.number('count', {
label: 'Orders',
format: 'integer',
}),
})
)
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Orders by source",
"chart": {
"kind": "bar-list",
"fields": {
"label": {
"kind": "text",
"name": "source",
"label": "Source"
},
"value": {
"kind": "number",
"name": "count",
"format": "integer",
"label": "Orders"
}
},
"data": [
{
"source": "Search",
"count": 412
},
{
"source": "Referral",
"count": 231
},
{
"source": "Direct",
"count": 128
}
]
}
}
]
}
}
}Line chart
A line chart plots a value over an axis, one point per row and one line per
series. The x field places each row on the axis and the series are number
fields, each drawn in the theme’s chart colour of its index. The ticks, the
tooltip, and the legend all read from the fields.
setOptions takes showXAxis, showYAxis, showGridLines, showLegend, each
true by default, and curveType: linear, monotone, or step. onClick
takes a link target, the same as a table row, and a click on a point follows it.
import {
charts,
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const days = [
{ day: '2026-09-01', revenue: 1240, refunds: 80 },
{ day: '2026-09-02', revenue: 980, refunds: 0 },
{ day: '2026-09-03', revenue: 1510, refunds: 120 },
{ day: '2026-09-04', revenue: 1320, refunds: 40 },
{ day: '2026-09-05', revenue: 1780, refunds: 60 },
]
orders.view('overview', () =>
widget()
.setTitle('Revenue')
.setDescription('Daily, this week')
.setChart(
charts
.line(days)
.setFields({
x: fields.date('day', {
label: 'Day',
precision: 'day',
}),
series: [
fields.number('revenue', {
label: 'Revenue',
format: 'currency',
currency: 'USD',
}),
fields.number('refunds', {
label: 'Refunds',
format: 'currency',
currency: 'USD',
}),
],
})
.setOptions({ curveType: 'monotone' })
)
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Revenue",
"description": "Daily, this week",
"chart": {
"kind": "line",
"fields": {
"x": {
"kind": "date",
"name": "day",
"precision": "day",
"label": "Day"
},
"series": [
{
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": "Revenue"
},
{
"kind": "number",
"name": "refunds",
"format": "currency",
"currency": "USD",
"label": "Refunds"
}
]
},
"options": {
"curveType": "monotone"
},
"data": [
{
"day": "2026-09-01",
"revenue": 1240,
"refunds": 80
},
{
"day": "2026-09-02",
"revenue": 980,
"refunds": 0
},
{
"day": "2026-09-03",
"revenue": 1510,
"refunds": 120
},
{
"day": "2026-09-04",
"revenue": 1320,
"refunds": 40
},
{
"day": "2026-09-05",
"revenue": 1780,
"refunds": 60
}
]
}
}
]
}
}
}The context
The title, the description, the footnote, and the caption frame the data. The footnote reads the data, a trend or a comparison, and takes a tone. The caption is a muted line for the range or an exclusion.
import {
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const count = fields.number('count', { format: 'integer' })
orders.view('overview', () =>
widget()
.setTitle('Subscribers', { icon: 'users' })
.setDescription('Last 30 days')
.setStat({
value: 12_480,
field: count,
prior: 11_900,
delta: 'percent',
})
.setFootnote('Trending up this month', {
icon: 'trending-up',
tone: 'success',
})
.setCaption('Compared with the 30 days before')
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Subscribers",
"titleIcon": "users",
"description": "Last 30 days",
"footnote": "Trending up this month",
"footnoteIcon": "trending-up",
"footnoteTone": "success",
"caption": "Compared with the 30 days before",
"stat": {
"value": 12480,
"field": {
"kind": "number",
"name": "count",
"format": "integer",
"label": ""
},
"prior": 11900,
"delta": "percent"
}
}
]
}
}
}Title icon
Set an icon name for the renderer to show beside the title. The protocol contains the icon name, not an image or a glyph.
import {
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const count = fields.number('count', { format: 'integer' })
orders.view('overview', () =>
widget()
.setTitle('Open orders', { icon: 'package-open' })
.setStat({ value: 128, field: count })
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Open orders",
"titleIcon": "package-open",
"stat": {
"value": 128,
"field": {
"kind": "number",
"name": "count",
"format": "integer",
"label": ""
},
"delta": "absolute"
}
}
]
}
}
}All options
The context, the stat with a change, and a chart on the same widget.
import {
charts,
defineResource,
fields,
widget,
} from '@backlit/sdk'
const orders = defineResource('orders', [])
const revenue = fields.number('revenue', {
format: 'currency',
currency: 'USD',
})
orders.view('overview', () =>
widget()
.setTitle('Total revenue', {
icon: 'circle-dollar-sign',
})
.setDescription('Last 30 days')
.setStat({
value: 48_120,
field: revenue,
prior: 42_964,
delta: 'percent',
tone: 'success',
description: 'Ahead of the quarterly target',
descriptionIcon: 'trending-up',
})
.setChart(
charts
.sparkArea([
{ day: 'Mon', revenue: 12 },
{ day: 'Tue', revenue: 18 },
{ day: 'Wed', revenue: 15 },
{ day: 'Thu', revenue: 22 },
{ day: 'Fri', revenue: 30 },
])
.setFields({
x: fields.text('day'),
series: revenue,
})
)
.setFootnote('Trending up this month', {
icon: 'trending-up',
tone: 'success',
})
.setCaption('Compared with the 30 days before')
)
export default orders{
"kind": "success",
"status": 200,
"node": {
"kind": "view",
"resource": "orders",
"name": "overview",
"slots": {
"content": [
{
"kind": "widget",
"title": "Total revenue",
"titleIcon": "circle-dollar-sign",
"description": "Last 30 days",
"footnote": "Trending up this month",
"footnoteIcon": "trending-up",
"footnoteTone": "success",
"caption": "Compared with the 30 days before",
"stat": {
"value": 48120,
"field": {
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
},
"prior": 42964,
"delta": "percent",
"tone": "success",
"description": "Ahead of the quarterly target",
"descriptionIcon": "trending-up"
},
"chart": {
"kind": "spark-area",
"fields": {
"x": {
"kind": "text",
"name": "day",
"label": ""
},
"series": [
{
"kind": "number",
"name": "revenue",
"format": "currency",
"currency": "USD",
"label": ""
}
]
},
"data": [
{
"day": "Mon",
"revenue": 12
},
{
"day": "Tue",
"revenue": 18
},
{
"day": "Wed",
"revenue": 15
},
{
"day": "Thu",
"revenue": 22
},
{
"day": "Fri",
"revenue": 30
}
]
}
}
]
}
}
}See Charts for all chart options and category bars.