Empty state
In reviewLive · web
Variants: each owns an empty content region, centered on both axes
No sessions yet
Spin up your first Cloud Agent session and it’ll show up here.
No results
Type above to filter, then try a different search or clear the filter.
Couldn’t load files
The file tree failed to load. Check your connection and try again.
Compact: a small inline empty for a short list (denser rhythm, no region chrome)
No unread notifications
You’re all caught up.
Created 0, retried 0, filter untouched · the island is live.
// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,// not yet exported from @dizyx/nockerl-react; promotion is tracked.)import { NockerlButton } from '@dizyx/nockerl-react';import { Inbox, Plus, RotateCcw } from 'lucide-react';
// First-run onboarding: the brand mark + a primary call-to-action.<EmptyState icon={<Plus />} tone="brand" title="No sessions yet" description="Spin up your first Cloud Agent session and it'll show up here." actions={<NockerlButton text="Create session" variant="primary" onClick={createSession} />}/>;
// No search results: a hint + a recover action (title switches on the query).<EmptyState icon={<Inbox />} title={query ? 'No matches' : 'No results'} description="Try a different search or clear the filter." actions={<NockerlButton text="Clear filter" variant="secondary" onClick={clearFilter} />}/>;
// Failed to load: the warm error tone + a Retry.<EmptyState icon={<RotateCcw />} tone="error" title="Couldn't load files" description="Check your connection and try again." actions={<NockerlButton text="Retry" variant="tertiary" onClick={reload} />}/>;// Android: Jetpack Compose (canonical). The shipped pattern is a centered Column// (a 48dp muted icon → bodyMedium message → optional NockerlButton), e.g.// files/ui/FilesSheet.kt, inbox/ui/InboxSheet.kt, chat/ui/MessageList.kt.import androidx.compose.foundation.layout.*import androidx.compose.material3.Iconimport androidx.compose.material3.Textimport androidx.compose.ui.Alignmentimport com.nockerl.app.core.ui.NockerlButtonimport com.nockerl.app.core.ui.NockerlButtonVariant
@Composablefun EmptyState( icon: ImageVector, title: String, description: String? = null, action: (@Composable -> Unit)? = null,) { val colors = LocalNockerlColors.current Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { Icon(icon, contentDescription = null, modifier = Modifier.size(48.dp), tint = colors.onCardAltMuted.copy(alpha = 0.5f)) Spacer(Modifier.height(16.dp)) Text(title, style = MaterialTheme.typography.titleMedium, color = colors.onCard) description?.let { Spacer(Modifier.height(4.dp)) Text(it, style = MaterialTheme.typography.bodyMedium, color = colors.onCardAltMuted) } action?.let { Spacer(Modifier.height(16.dp)); it() } }}
// Failed-to-load: pass a Retry as the action.EmptyState(Icons.Filled.FolderOpen, "Couldn't load files", "Check your connection and try again.") { NockerlButton(text = "Retry", onClick = ::reload, variant = NockerlButtonVariant.TERTIARY)}// macOS: SwiftUI (canonical). Nockerl Voice uses the SYSTEM ContentUnavailableView// for empty + no-results states (UI/HistoryView.swift, UI/SettingsView.swift).// systemImage is an SF Symbol; description is a Text. No-results gets its own title.if filtered.isEmpty { ContentUnavailableView( records.isEmpty ? "No transcriptions yet" : "No matches", systemImage: "waveform", description: Text(records.isEmpty ? "Your dictations will appear here." : "Try a different search.") ) .frame(maxWidth: .infinity, maxHeight: .infinity)}
// With an action, use the closure form (SwiftUI's built-in empty-state slot):ContentUnavailableView { Label("Couldn't load files", systemImage: "exclamationmark.triangle")} description: { Text("Check your connection and try again.")} actions: { Button("Retry") { reload() }.buttonStyle(.nockerlSecondary)}Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
title * | string | The headline. Bound to the title.large type role. | |
icon | ReactNode | A stroke glyph rendered in a recessed icon well above the title (muted by default). | |
description | string | Supporting line under the title (body.medium role). Keep it to one or two short sentences. | |
tone | 'neutral' | 'brand' | 'error' | 'neutral' | Icon-well treatment: neutral muted, brand cyan (first-run), error warm status (failed-to-load). Never color alone, always paired with icon + text. |
actions | ReactNode | Optional call-to-action(s), typically one primary Button plus an optional ghost. Centered under the description. | |
compact | boolean | false | Denser rhythm + smaller well for a short inline list rather than a full region. |
| Parameter | Type | Default | Description |
|---|---|---|---|
title * | String | The headline. Rendered with typography.titleMedium. | |
icon | ImageVector | Leading illustration. Drawn at 48.dp, tinted onCardAltMuted @ alpha 0.5. | |
description | String? | null | Supporting line (typography.bodyMedium, onCardAltMuted). Context-aware per filter tab in lists (e.g. "No unread notifications"). |
modifier | Modifier | Modifier.fillMaxSize() | The centered Column fills the region (Arrangement.Center, CenterHorizontally). |
action | (@Composable -> Unit)? | null | Optional trailing action, typically a NockerlButton. The error arm uses TERTIARY. |
SwiftUI uses the system ContentUnavailableView; there is no custom Nockerl
type. State (empty vs. no-results) is decided by the caller and reflected in the
title/description; the actions/label come from view-builder closures.
| Parameter | Type | Default | Description |
|---|---|---|---|
title * | LocalizedStringKey | The headline (first positional argument). No-results gets its own title ("No matches"). | |
systemImage * | String | An SF Symbol name (e.g. "waveform") drawn above the title. | |
description | Text? | nil | Supporting line under the title. |
actions | () -> View | Closure-form only: a trailing actions builder (e.g. a Retry Button with a Nockerl ButtonStyle). Not available on the short initializer. |