Skip to content
Backlit
Esc
navigateopen⌘Jpreview
On this page

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.

Was this page helpful?