# Display widgets

> Source: https://docs.clonepartner.com/reference/widgets/display/

<!-- sources: src/dashboard/register-widget-types.ts, src/dashboard-parser.ts, ui/src/components/dashboard/widgets/DashboardStatWidget.vue, ui/src/components/dashboard/widgets/DashboardProgressWidget.vue, references/envoy/dashboards.md -->

# Display widgets

Read-only widgets that present copy, numbers, and status: `heading`, `stat`, `progress`, `steps`, and `people`. Keys shared by every widget (`id`, `title`, `width`, `scope`, `refresh_interval_seconds`) are documented on the [Widgets overview](/reference/widgets/).

## `heading`

Non-data copy between widgets: a compact section label or an instructional panel. No connector call.

| Key | Type | Required | Description |
|---|---|---|---|
| `markdown` | string | No | Content; supports `##` headings, paragraphs, lists, and bold. |
| `variant` | string | No | `section` (default) — compact uppercase label; `callout` — tinted instructional panel. Any other value is rejected. |

Set `variant` explicitly; there is no content-based auto-detection. Use `callout` for instructions customers should read before interacting (for example above `field_mapping` editors), and `section` to divide widget groups.

```yaml
- id: intro
  type: heading
  variant: callout
  width: 4
  markdown: |
    ## How it works
    Pick a field on the **left** and its match on the **right**, then click **Save mapping**.
```

## `stat`

A single numeric result, from a direct connector call or a shared query.

| Key | Type | Required | Description |
|---|---|---|---|
| `data` | object or JSONata string | Yes, unless `shared_query` is set | Read-only call config `{ connector, method, args? }`. |
| `shared_query` | string | No | Name of a `shared_queries` entry to reuse instead of `data`. |
| `value_expr` | string | Yes, when `shared_query` is set | JSONata over the shared result (bound as `data`) producing the scalar. |
| `unit` | string | No | Display suffix. |
| `compare_to` | object or JSONata string | No | Comparison call config. |

When the result is not already a number, the renderer picks `count`, `value`, or `n` from the first row of an array result (or from an object result), and falls back to an array's length.

```yaml
- id: tickets_total
  type: stat
  title: Tickets
  unit: tickets
  data:
    connector: mongo
    method: count
    args:
      collection: zendesk_tickets
```

Missing `data` without a `shared_query` fails with `<path>.data is required for stat`; a `shared_query` without `value_expr` fails with `<path>.value_expr is required when shared_query is set`.

## `progress`

A current/total ratio bar.

| Key | Type | Required | Description |
|---|---|---|---|
| `current` | object or JSONata string | Yes, unless `shared_query` is set | Call config producing the numerator. |
| `total` | object or JSONata string | Yes, unless `shared_query` is set | Call config producing the denominator. |
| `shared_query` | string | No | Reuse a `shared_queries` entry instead of two calls. |
| `current_expr` | string | Yes, when `shared_query` is set | JSONata producing the numerator from the shared result. |
| `total_expr` | string | Yes, when `shared_query` is set | JSONata producing the denominator from the shared result. |

Like `stat`, non-numeric results resolve through `count` or `value` on the first row.

```yaml
- id: ticket_progress
  type: progress
  title: Ticket migration
  current:
    connector: mongo
    method: count
    args:
      collection: zendesk_tickets
      filter: { status: migrated }
  total:
    connector: mongo
    method: count
    args:
      collection: zendesk_tickets
```

## `steps`

A horizontal process stepper driven by one `steps` value.

| Key | Type | Required | Description |
|---|---|---|---|
| `steps` | array or JSONata string | Yes | Literal array of step items, or an expression returning one. |
| `shared_query` | string | No | Expose a cached query to the expression as `data`/`result`. |

Each step item supports:

| Field | Required | Description |
|---|---|---|
| `label` | Yes | Display text. Steps with empty labels are ignored. |
| `subtext` | No | Small helper text. |
| `current` | No | Marks the active step; steps before it render as completed. When more than one step sets it, the first wins. |
| `completed` | No | Explicit override. A step may set both `current: true` and `completed: true` to render the final step as done while keeping the active ring. |

The expression is evaluated over the dashboard context (`arguments`, `job`, `job_config`, `shared`) and must return an array:

```yaml
- id: migration_steps
  type: steps
  title: Migration plan
  width: 4
  steps: |
    (
      $current := job_config.migration_step.stage ? job_config.migration_step.stage : "connect_accounts";
      [
        { "label": "Connect accounts", "current": $current = "connect_accounts" },
        { "label": "Checklist completion", "current": $current = "checklist_completion" },
        { "label": "Sample migration", "current": $current = "sample_migration" }
      ]
    )
```

Prefer a job template `job_config` entry as the stage source when Envoy owns the state; use a `shared_query` to external storage only when another system owns the status.

## `people`

Compact contact cards driven by one `people` value.

| Key | Type | Required | Description |
|---|---|---|---|
| `people` | array or JSONata string | Yes | Literal array of person items, or an expression returning one. |
| `shared_query` | string | No | Expose a cached query to the expression as `data`/`result`. |

Each person item supports:

| Field | Required | Description |
|---|---|---|
| `label` | Yes | Role or label. People with empty labels or names are ignored. |
| `name` | Yes | Display name. |
| `email` | No | Rendered as a `mailto:` link. |
| `phone` | No | Rendered as a `tel:` link. |
| `placeholder` | No | Short bio, ownership note, or when-to-reach-out instructions. |
| `icon` | No | Lucide icon name such as `user-round`, `briefcase-business`, `mail`, `phone`, or `life-buoy`. |
| `gravatar` | No | When `true` and `email` is present, the backend returns a Gravatar URL. |
| `gravatar_url` | No | Explicit avatar URL; overrides the generated Gravatar URL. |

```yaml
- id: migration_people
  type: people
  title: Migration team
  width: 4
  people: |
    (
      $people := job_config.migration_people;
      [
        {
          "label": "Gorgias IM",
          "name": $people.gorgias_im_name,
          "email": $people.gorgias_im_email,
          "placeholder": "Reach out for Gorgias-side approvals.",
          "icon": "briefcase-business",
          "gravatar": true
        }
      ]
    )
```

## Public share behavior

`heading` passes through with its markdown. `stat` and `progress` keep only display keys — connector call configs are stripped. For `steps` and `people`, literal arrays pass through, but JSONata expression strings are stripped (they would leak author logic), so expression-driven steppers render empty on the public surface. See [Authoring dashboards](/building/authoring-dashboards/).

## Related

- [Widgets overview](/reference/widgets/) — shared keys and data binding
- [Chart and table widgets](/reference/widgets/charts-and-table/) — the other read-only group
- [Dashboard YAML](/reference/dashboards/) — `shared_queries` and scope profiles
