Empty
Wyjaśnia, dlaczego nie ma nic do pokazania i co zrobić dalej — przy pierwszym użyciu, braku wyników, gdy wszystko jest już zrobione i przy braku dostępu.
import { FolderPlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"
export default function EmptyDemo() {
return (
<Empty className="max-w-sm p-4 md:p-6">
<EmptyHeader>
<EmptyMedia variant="icon">
<FolderPlusIcon />
</EmptyMedia>
<EmptyTitle>Nie masz jeszcze projektów</EmptyTitle>
<EmptyDescription>
Pierwszą aplikację wdrożysz w około minutę.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">Utwórz projekt</Button>
</EmptyContent>
</Empty>
)
}Anatomia
- 1KontenerWyśrodkowany stos, który zajmuje miejsce brakującej treści. Na stronach wariant outline dodaje przerywane obramowanie.
- 2AkcjeJeden jasny następny krok — zwykle akcja, która tworzy pierwszy element.
- 3GrafikaIkona na wyniesionym kafelku albo ilustracja, która nadaje kontekst.
- 4OpisJedno zdanie o tym, dlaczego jest pusto i co z tym zrobić.
- 5TytułMówi prostymi słowami, czego brakuje.
Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/empty.jsonUżycie
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<FolderPlusIcon />
</EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>Deploy your first app in about a minute.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Create project</Button>
</EmptyContent>
</Empty>Przykłady
Z obramowaniem
variant="outline" rysuje przerywaną ramkę — tak zwyczajowo oznacza się obszar, który czeka na treść, np. strefę upuszczania plików albo wolne miejsce w układzie.
import { UploadCloudIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"
export default function EmptyOutline() {
return (
<Empty variant="outline" className="max-w-lg">
<EmptyHeader>
<EmptyMedia variant="icon">
<UploadCloudIcon />
</EmptyMedia>
<EmptyTitle>Prześlij pierwszy zbiór danych</EmptyTitle>
<EmptyDescription>
CSV, Parquet lub JSON do 2 GB. Kolumny zostaną wykryte automatycznie.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button variant="outline" size="sm">
Wybierz pliki
</Button>
</EmptyContent>
</Empty>
)
}Brak wyników
Gdy wyszukiwanie lub filtr niczego nie znajduje, powtórz zapytanie, podpowiedz możliwą przyczynę i zaproponuj drogi wyjścia, które nie przekreślają dotychczasowej pracy.
import { SearchIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"
export default function EmptyNoResults() {
return (
<Empty className="max-w-md">
<EmptyHeader>
<EmptyMedia variant="icon">
<SearchIcon />
</EmptyMedia>
<EmptyTitle>Brak wyników dla „zasady zwrotów”</EmptyTitle>
<EmptyDescription>
Sprawdź pisownię, użyj mniejszej liczby słów lub usuń filtr „Ostatnie 7 dni”.
</EmptyDescription>
</EmptyHeader>
<EmptyContent className="flex-row justify-center gap-2">
<Button variant="outline" size="sm">
Usuń filtr
</Button>
<Button variant="ghost" size="sm">
Wyczyść wyszukiwanie
</Button>
</EmptyContent>
</Empty>
)
}Wszystko na bieżąco
Stan pusty bywa dobrą wiadomością. Powiedz to wprost i nie wymyślaj wezwania do działania, gdy nie ma nic do zrobienia.
import { CircleCheckIcon } from "lucide-react"
import {
Empty,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"
export default function EmptyCleared() {
return (
<Empty className="max-w-sm">
<EmptyHeader>
<EmptyMedia variant="icon" className="text-success-11">
<CircleCheckIcon />
</EmptyMedia>
<EmptyTitle>Jesteś na bieżąco</EmptyTitle>
<EmptyDescription>
Nie masz oczekujących recenzji. Nowe prośby pojawią się tutaj.
</EmptyDescription>
</EmptyHeader>
</Empty>
)
}Brak dostępu
Gdy treść istnieje, ale ktoś nie może jej zobaczyć, napisz, czyjej zgody potrzebuje i jak o nią poprosić.
import { LockIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"
export default function EmptyRestricted() {
return (
<Empty className="max-w-sm">
<EmptyHeader>
<EmptyMedia variant="icon">
<LockIcon />
</EmptyMedia>
<EmptyTitle>Nie masz dostępu do sekcji Płatności</EmptyTitle>
<EmptyDescription>
Faktury mogą przeglądać tylko właściciele przestrzeni roboczej. Poproś Mayę Chen o dostęp.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm" variant="outline">
Poproś o dostęp
</Button>
</EmptyContent>
</Empty>
)
}W karcie
W karcie lub tabeli stan pusty zajmuje miejsce treści — tytuł i akcje wokół niego zostają, więc kontekst nigdy nie ginie.
import { PlugIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty"
export default function EmptyInCard() {
return (
<Card className="w-full max-w-lg">
<CardHeader>
<CardTitle>Integracje</CardTitle>
<CardDescription>Synchronizuj zdarzenia wdrożeń ze swoimi narzędziami.</CardDescription>
<CardAction>
<Button size="sm" variant="outline">
Przeglądaj
</Button>
</CardAction>
</CardHeader>
<CardContent>
<Empty variant="outline" className="p-6 md:p-8">
<EmptyHeader>
<EmptyMedia variant="icon">
<PlugIcon />
</EmptyMedia>
<EmptyTitle>Brak połączonych integracji</EmptyTitle>
<EmptyDescription>
Połącz Slacka lub Lineara, aby publikować informacje o wdrożeniach tam, gdzie pracuje twój zespół.
</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button size="sm">Połącz ze Slackiem</Button>
</EmptyContent>
</Empty>
</CardContent>
</Card>
)
}Wersję dla tabel pokazuje przykład pustej tabeli.
Wytyczne
Kiedy używać
- Pierwsze użycie. Funkcja nie ma jeszcze danych. Wyjaśnij, co daje, i zaproponuj pierwszy krok.
- Brak wyników. Wyszukiwanie lub filtr niczego nie znalazły.
- Wszystko załatwione. Nie zostało nic do zrobienia — pusta skrzynka odbiorcza, opróżniona kolejka przeglądów.
- Brak dostępu. Treść istnieje, ale brakuje uprawnień, żeby ją zobaczyć.
Kiedy nie używać
- Przy błędach, które przerywają zadanie — użyj komponentu Alert z akcją, która pozwala naprawić problem.
- Podczas ładowania danych — użyj komponentu Skeleton w kształcie treści. Nigdy nie pozwól, by stan pusty mignął, zanim dane dotrą.
Jak pisać
- Tytuł — opisz sytuację prostymi słowami: Nie masz jeszcze projektów, Brak wyników dla „polityka zwrotów”. Unikaj obwiniania i wykrzykników.
- Opis — jedno lub dwa zdania o tym, dlaczego jest pusto i co będzie dalej.
- Akcje — jedna główna akcja, która wypełni puste miejsce. Drugą dodaj tylko wtedy, gdy istnieje realna alternatywa.
Ikony i ilustracje
Użyj ikony w EmptyMedia variant="icon" — od razu mówi, jakiego obiektu brakuje. Ilustracje zachowaj dla stanów pierwszego użycia na ekranach o marketingowym charakterze; w gęstym interfejsie produktu konkurują z treścią wokół nich.
Dostępność
- To treść, nie alert. Stan pusty to zwykła treść — nie nadawaj mu
role="alert". Gdy pojawia się w odpowiedzi na wyszukiwanie, ogłoś zamiast tego liczbę wyników w regionie na żywo w trybie polite („0 wyników”). - Tytuł domyślnie nie jest nagłówkiem.
EmptyTitlerenderuje<div>. Gdy stan pusty zajmuje całą stronę lub sekcję, umieść w nim nagłówek odpowiedniego poziomu. - Ikony są dekoracyjne. Znaczenie niesie tytuł; ikony w
EmptyMedianie potrzebują etykiet. - Fokus. Gdy ktoś wyczyści filtr przyciskiem w stanie pustym, przenieś fokus na wyniki lub pole wyszukiwania, żeby osoby korzystające z klawiatury nie straciły orientacji.
Dokumentacja API
Empty
Wyśrodkowany kontener z wewnętrznymi odstępami. Przyjmuje wszystkie propsy div.
EmptyMedia
EmptyHeader
Grupuje grafikę, tytuł i opis oraz ogranicza ich szerokość, żeby wygodnie się je czytało.
EmptyTitle
Sytuacja w kilku słowach, złożona stylem heading-sm.
EmptyDescription
Jedno lub dwa zdania w kolorze muted-foreground. Linki w opisie mają gotowe style.
EmptyContent
Akcje i pomocnicze kontrolki pod nagłówkiem.