Cloud|

@k2b/ui

Empty states

Compact and panel placeholders plus route-level not-found states.

Placeholder@k2b/uiNotFoundState@k2b/ui

Empty icons sit in soft chips of the app color; loading, error, and inline icons stay plain. The examples name each non-default layout choice. NotFoundState handles a route-level dead end.

No projects

Create the first project.

No projects

Create the first project.

Loading projects

Fetching the latest projects.
404

Project not found

It may have been moved.

All projects
No notes yet

TSX

Copy
import { Button, NotFoundState, Placeholder } from "@k2b/ui";
<Placeholder
surface="paper"
title="No projects"
description="Create the first project."
icon="ti ti-folder-off"
action={<Button size="sm">New project</Button>}
/>
<Placeholder surface="paper" variant="panel" icon="ti ti-folder-off" title="No projects" description="Create the first project." />
<Placeholder surface="paper" state="loading" variant="panel" title="Loading projects" description="Fetching the latest projects." />
<Placeholder surface="paper" state="error" align="left" title="Projects unavailable" description="Reload the page to try again." />
<NotFoundState code="404" title="Project not found" description="It may have been moved." action={{ label: "All projects", href: "/projects" }} />
<Placeholder surface="paper" variant="inline" align="left" icon="ti ti-file-text" description="No notes yet" />

Placeholder explains an empty, filtered, inaccessible, loading, or failed region inside a page. NotFoundState handles a whole-page dead end and can provide one route back.

The application owns the message and any recovery action.

Use empty states

Use Placeholder for an empty table, loading panel, or section that failed to load. Use the compact variant inside a section and the panel variant for a whole work area; the panel variant centers its content in the height it receives, such as class="flex-1" in a column. Use the inline variant with align="left" for an empty list or sidebar section: one line with a small icon where the first row would be.

state controls semantics and the default icon, while variant and align control layout independently. Name non-default layout choices in examples so different states are not mistaken for different component implementations.

Use surface="paper" only when the placeholder stands alone in a page area, such as an empty list that replaces a table. Inside a surface that already frames it, such as a section, dialog, detail panel, or Paper, leave surface at its default. One frame per surface: a paper placeholder inside Paper, SettingsSection, PanelDialog, a prompt dialog, or DetailPanel keeps its layout but draws no border, background, or shadow of its own.

Use NotFoundState when a requested page or resource cannot be shown. Display a code such as 404 only when it accurately describes the result.

Use state="empty" for “Nothing here yet”, state="no-results" for “No matches” after filtering, and state="no-access" for “You do not have access”. Keep these states distinct so the next step is clear. They draw no box inside AppWorkspace, even with surface="paper". AppOverview.EmptyState is a plain panel placeholder without a paper panel.

Every load error offers Try again through state="error" and onRetry. Preserve the person's selection and filters during recovery. For tables, use DataTable loading placeholders at the real row height and keep existing rows while refreshing.

Import

tsx
import {
  Button,
  NotFoundState,
  Placeholder,
} from "@k2b/ui";

Properties

Placeholder

Property Type Default Purpose
title JSX.Element none Names the state.
description JSX.Element none Explains the state or next step.
icon string state-dependent Overrides the Tabler icon class. empty has no default icon; no-results uses search-off and no-access uses lock.
action JSX.Element none Adds a recovery or creation action.
onRetry () => void none Appends a localized Try again button after action for errors.
state "empty" | "no-results" | "no-access" | "loading" | "error" "empty" Selects state semantics and default icon.
variant "compact" | "inline" | "panel" "compact" Chooses stacked section spacing, one list row, or a larger work-area treatment.
surface "none" | "paper" "none" Adds the shared paper surface for a standalone page area.
align "center" | "left" "center" Aligns the content.
class string none Adds classes to the root element.

An error state uses role="alert". A loading state uses role="status", aria-live="polite", and aria-busy="true". The loading icon stops spinning when reduced motion is requested. Empty, no-results, and no-access icons sit in a soft chip of the accent ink --k2b-app-workspace-active; loading and error keep their status colors without a chip, and inline placeholders keep a plain glyph. The icon box keeps the same size in every state: 2rem for compact, 2.5rem for panel, and 1rem for inline. Pass state="loading" or state="error" for a loading or failure message instead of a loader or alert icon on the default empty state; the placeholder-states repository check reports such a call.

NotFoundState

Property Type Default Purpose
title string required States what cannot be shown.
description string none Adds useful context without exposing internal errors.
code string none Displays a large status code.
icon string none Displays an icon when no code is set.
action { label: string; href: string; icon?: string } none Provides one navigation path out of the dead end.

NotFoundState renders a div, not a main, so the page shell retains ownership of landmarks.

Accessibility

Write titles that distinguish an empty result from a failed request. Show an empty state only after a read that succeeded: a failed read shows its error, not a claim that nothing is there. While the same region reloads, keep the empty state in place instead of swapping in an empty frame. Loading text should name what is loading. The built-in recovery button says Try again (Erneut versuchen in German). Empty, no-results, and no-access have no role or live announcement.

When an action fills the region, such as adding the first record, the placeholder and its focused button leave. Move focus to the new content, such as the created row, so it does not fall back to the page.

NotFoundState renders its title as an h1. Do not place another page-level heading around it.

Runtime

Both components render on the server. An anchor action works without browser code; an island owns any callback-based action.

Example

tsx
<Placeholder
  surface="paper"
  title="No notes yet"
  description="Create the first note for this workspace."
  action={<Button size="sm" onClick={createNote}>Create note</Button>}
/>

<Placeholder
  surface="paper"
  state="loading"
  variant="panel"
  title="Loading notes"
/>

<Placeholder
  state="error"
  align="left"
  title="Notes unavailable"
  description="Your selection is kept."
  onRetry={reloadNotes}
/>

<Placeholder
  variant="inline"
  align="left"
  icon="ti ti-file-text"
  description="No notes yet"
/>

<NotFoundState
  code="404"
  title="Application not found"
  description="The application may have moved or access may have changed."
  action={{ label: "Back to applications", href: "/apps" }}
/>

Connect a coding agent

The Cloud skill provides compact working instructions. MCP supplies exact current documentation when details matter.

CLI command
bunx skills add https://docs.example.com

This command installs the working instructions published by this website in the selected coding agent.

For exact, current details, also connect the MCP server through one of the agent tabs.

Installation uses the open-source Vercel Skills CLI.

The skill and MCP complement each other: the skill describes workflows, while MCP supplies current documentation.