EmptyState
Preview
Basic
Loading…
Preview
Code
ts
import EmptyState from '@/components/layout/EmptyState';src/components/layout/EmptyState.tsx
AI prompt
text
Build an empty-state placeholder component in React + TypeScript + Tailwind CSS.
A quiet placeholder for an empty list or table.
## Look
- `flex flex-col items-center justify-center gap-1 py-16 text-center`.
- Title: `text-sm font-medium text-slate-700 dark:text-slate-200`, as a `flex items-center gap-1.5` line.
- The optional hint does NOT sit as a second line under the title: it hides behind a 14px lucide `Info` (ⓘ) button right after the title (`text-slate-400 hover:text-slate-600`, dark `slate-500 → slate-300`, rounded, focus ring), aria-label "Why is this empty?". Hovering or focusing it shows the hint in a small dark tooltip bubble above — the same ⓘ treatment every other hint in the kit uses.
## API
`title: string`, `hint?: string`.
## Demo
Inside a card: title "No projects yet", hint "Create one to get started."
## House style (applies to everything above)
- Stack: React 19 + TypeScript + Tailwind CSS v4, icons from lucide-react. One self-contained file; default-export the component and named-export its types. `'use client'` if it has state, refs or handlers.
- Font Inter; palette indigo on slate. Primary accent indigo-600 (hover indigo-700, dark mode indigo-400). Body text slate-700 / dark slate-200; secondary slate-500 / dark slate-400.
- Dark mode is a `.dark` class on <html> (not prefers-color-scheme). Every colour needs its `dark:` pair.
- Compact admin scale: text-xs (12px) for controls and body, 10–11px for meta, rounded-lg (8px) controls, rounded-2xl (16px) cards.
- Card surface ("panel"): `bg-white/60 dark:bg-slate-800/60 backdrop-blur-xl border border-white/60 dark:border-slate-700/60 rounded-2xl shadow-lg`, on a soft slate gradient page background.
- Floating surfaces (dropdowns, popovers, menus) are OPAQUE: `bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-2xl shadow-lg`, no backdrop blur (it creates a stacking context that traps the popover's z-index). In-flow popovers are z-50; portalled overlays z-200.
- Text inputs and select triggers: `w-full px-3 py-2 text-xs rounded-lg border border-slate-300 dark:border-slate-700 bg-white/80 dark:bg-slate-900/60 placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/40 focus:border-indigo-500`.
- Field labels: 11px semibold slate-600. Section titles: 10px semibold uppercase wide-tracking slate-500.
- Primary button: indigo-600 fill, white 12px semibold text, rounded-lg, px-3 py-2, disabled at 50% opacity. Ghost button: slate-600 text, hover slate-100.
- Popovers close on outside click AND on Escape (listen to both; include the portalled panel's element in the outside-click check).
- Don't nest scroll containers around popovers: an ancestor with overflow hidden/auto clips an absolutely-positioned dropdown. Portal the panel to <body> when it must escape a scroller, and reposition it on scroll and resize.
- Accessible by default: visible focus rings, keyboard support that matches the WAI-ARIA pattern for the widget, `aria-label` on icon-only buttons, `min-w-0` so text truncates instead of overflowing.Source
tsx
import { InfoTooltip } from '@/components/overlay/Tooltip';
/* Origin: bonus-adjustment (96S2), verbatim. The hint sits behind an ⓘ
rather than under the title, like every other hint in the kit. */
export default function EmptyState({ title, hint }: { title: string; hint?: string }) {
return (
<div className="flex flex-col items-center justify-center py-16 text-center gap-1">
<p className="flex items-center gap-1.5 text-sm font-medium text-slate-700 dark:text-slate-200">
{title}
{hint && <InfoTooltip content={hint} label="Why is this empty?" />}
</p>
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title* | string | — | |
hint | string | — |