Open the console

Empty-state onboarding: the screen that beats the email

A new account has no data, so its first screen carries the whole load. What an empty state must contain, and how to drive it from your real step list.

Empty-state onboarding: the screen that beats the email

Empty-state onboarding is the part of onboarding nobody schedules: the screen a new account sees before it has anything in it. No projects, no invoices, no teammates, no data. It is not a component you chose to build — it is what the product renders when a list has nothing to put in it, and it shapes the first session at least as much as the checklist you did design.

The highest-reach screen you own

Compare the three surfaces you can put in front of a new account. A product tour fires once, at a moment you chose, and only in the session where it fires. A checklist is dismissible, and some people dismiss it in the first seconds — a click, remembered permanently on the subject. An empty screen has no such exit: every account without data meets it, including the people who dismissed the checklist, the people on a plan that has no list, and the people who signed up before you wrote one.

That reach is the argument for designing it, and the reason not to decorate the argument with a statistic. No number we hold says what share of new accounts meet an empty screen during their first session. That is something to instrument in your own product: count the renders of the screen in a first session and the actions taken from it, and you have the denominator everything below depends on.

Three parts, in this order

An empty screen that works contains three things and stops.

  • One sentence on what belongs here. Not the state — “No projects yet” — but the concept: “A project holds the datasets you can query.” This screen is often the first place the reader meets your vocabulary.
  • One primary action, as a button that does the thing rather than opening a page explaining the thing. It is the same first action your checklist names. If the two disagree, one of them is wrong.
  • One line of what the user gets, in their terms and without promises you cannot keep: “You will see your first chart appear here.”

Then remove the rest. An illustration with no instruction teaches nothing. A secondary “read the docs” button competes with the primary one — the documentation belongs as a quiet link for the reader who has already decided, not as the first path. And a skip button on an empty screen skips nothing, because there is nowhere to go.

One list, one order

The failure mode to avoid is two authors for one sequence. The empty screen is usually written by whoever owns that page, the checklist is configured in the console by whoever owns onboarding, and the two drift apart: the screen sends the user to create an invoice while the checklist still waits on a payment account being connected.

Your instance already publishes the order. The state endpoint — GET /api/gs/v1/state — returns progress.nextKey: the first enabled step still pending, or an empty string when there is none. The step itself carries its words and its destination, as texts.ctaText and actionUrl. Read both from the widget instead of hardcoding them, and the empty screen cannot point somewhere the checklist does not expect:

const gs = document.querySelector('getting-started')

// gs.state and gs.config are read-only properties, populated at boot.
// The gs:loaded event fires again after refresh() and after identify().
function emptyState() {
  const state = gs.state
  const config = gs.config
  if (!state || !config) return null

  const key = state.progress.nextKey
  if (!key) return null // nothing pending: the real screen applies

  const step = config.steps.find((s) => s.key === key)
  track('empty_state_shown', { key })

  return { label: step.texts.label, cta: step.texts.ctaText, href: step.actionUrl }
}

Carry the step key on the button you render. The same key is what your analytics will join on, and what the completion call takes when the action happened on that screen: await gs.complete(key). When only the backend can see the action — an import that finished, a webhook delivered — the key goes to the server completion endpoint and stays distinguishable from client completions.

The empty state that finishes

A checklist has an empty state of its own, and you have already decided what it looks like whether or not you decided deliberately. When every required step is complete, the widget emits gs:completed-all and completedBehavior decides what is left on screen. It defaults to hide, so the panel removes itself having done its job, which is usually right: a checklist exists to become unnecessary.

That event is also the moment your empty screen should stop being empty. Whatever the user created — the first project, the first query, the first invoice — is now the thing to show them, not a congratulations panel about a list they finished. Use the event to refresh the view and let the product carry the praise.

Measure the screen, not the drawing

Two counts describe an empty screen honestly, and neither is a view count. Shown: the screen was rendered for a subject in a first session. Acted: the primary action was taken from it. The gap between the two is the screen's own drop-off, and it reads exactly like a checklist step does.

The widget does not record a per-step impression — it records a panel opening and a step being clicked, not a step being rendered — so the shown event for your own screen is yours to fire, which the snippet above does. Pair it with the key on the action, and read the two numbers next to the seen, clicked and completed counts of your checklist: the same funnel arithmetic applies, because it is the same list.

The cheapest test

Take the empty screen that renders most often in a first session. Rewrite it to the three parts above, point its button at the key nextKey already names, and ship it behind a split so half the new accounts keep the old screen. Then read your activation event per arm, after the window you wrote down in advance. One screen, one change, one date. There is no public benchmark to lean on here, because none was measured for this mechanism in your product.

If your product has no empty screen, because every new account is seeded with sample data, you made this same choice in another form: the seed is the onboarding, and it deserves the same three parts, the same order as the checklist, and the same two counts. To design that list in the first place, see how the checklist itself is designed; to watch the state your screen will read before any of it is live, open the preview, and the response shape is in the API reference.

Try it on your app