Cal.comdesign-system

Empty states

How Cal.com shows the absence of data — and what the user should do next.

Empty states convey that there is nothing to list or display on the current surface. Ideally they also give a clear next step.

In Cal.com product UI, prefer the Coss Empty primitive:

Legacy @calcom/ui EmptyScreen still exists in a few places — migrate those call sites to Empty when you touch them. Do not introduce new EmptyScreen usage.

Anatomy

Compose parts rather than a single prop bag:

  1. Empty — layout shell (centered column)
  2. EmptyHeader — icon + title + description group
  3. EmptyMedia variant="icon" — contextual icon from @coss/ui/icons
  4. EmptyTitle / EmptyDescription
  5. EmptyContent — optional actions (Button, secondary links)

Best practices

No data

Two situations:

  • Initial state — nothing created yet
  • Zero results — search or filters returned nothing

Initial state · presentational

Use when the user may be seeing the feature for the first time and needs a value prop plus a primary action. Prefer active language (“Add attributes”, “Create your first event type”) over passive “No X found”.

Presentational · initial
Add attributes
Attributes let you organize members — for example department, location, or seniority.
apps/web — attributes list (pattern)

Real call sites of this shape: org attributes, event types (first create), event-type webhooks, out-of-office, apps store empty.

Initial state · informational

Use when a CTA doesn’t belong here (action lives in the page header, or there’s simply nothing to do yet). Same Empty shell; omit EmptyContent.

Informational · initial
No upcoming bookings yet
You don’t have any upcoming bookings. As soon as someone books you, it’ll show up here.
apps/web — bookings list (pattern)

Chrome

Most full-page / list empties use a dashed border so the empty reads as a drop-in for the missing list:

Chrome · dashed border
Create your first link
Booking links let invitees find a time and book with you. Add one to get started.

When the empty sits inside the same card used for populated data (filters stay visible above), drop the extra border on Empty and nest it in CardPanel — keeps the transition from loading → empty → data stable.

In-card · filter / date range
No invoices found
No invoices in this date range.
apps/web — invoices table (pattern)

Zero results

After search or filters, keep the same presentation as the initial empty (same icon shell and chrome). Swap the title to search-aware copy — often t("no_result_found_for", { searchTerm }) — and keep a recovery action when one exists (create, clear search).

Zero results · search
No result found for “weekly sync”
Create a new link, or clear the search and try again.
apps/web — event types (pattern)

Do not invent a separate “no results” component for list pages that already use Empty.

Loading → empty

Empty states usually appear after a skeleton. Prefer:

  1. Show the list skeleton (or table chrome) while pending
  2. Swap to Empty only when the query has settled and the collection is empty

Avoid layout jump by keeping page headers / filter bars mounted in both states.

Copy

Always put user-facing strings through i18n (packages/i18n/locales/en/common.json or the route namespace).

Do / don’t

Do

  • Import from @coss/ui/components/empty
  • Pick a contextual icon (aria-hidden on decorative icons)
  • Offer a primary action in EmptyContent when the user can create or recover
  • Reuse one Empty for both initial and zero-results; branch on searchTerm / filters in the title

Don’t

  • Add new @calcom/ui EmptyScreen usage
  • Use Empty for loading or hard errors (use skeletons / Alert / toasts)
  • Ship copy-only empties with no context (“Nothing here”) when a next step exists
  • Nest a second dashed border when the parent card already provides the frame

Reference call sites