Przejdź do treści

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.

Kod źródłowyRejestr
Nie masz jeszcze projektów
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

Nie masz jeszcze projektów
  1. 1KontenerWyśrodkowany stos, który zajmuje miejsce brakującej treści. Na stronach wariant outline dodaje przerywane obramowanie.
  2. 2AkcjeJeden jasny następny krok — zwykle akcja, która tworzy pierwszy element.
  3. 3GrafikaIkona na wyniesionym kafelku albo ilustracja, która nadaje kontekst.
  4. 4OpisJedno zdanie o tym, dlaczego jest pusto i co z tym zrobić.
  5. 5TytułMówi prostymi słowami, czego brakuje.

Instalacja

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/empty.json

Uż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.

Prześlij pierwszy zbiór danych
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.

Brak wyników dla „zasady zwrotów”
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.

Jesteś na bieżąco
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ć.

Nie masz dostępu do sekcji Płatności
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.

Integracje
Brak połączonych integracji
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.
Nie masz jeszcze faktur
DobrzeKonkretny tytuł, pomocny następny krok, jedna akcja.
Ups! Nic tu nie ma!
ŹleOgólnikowy, emocjonalny tekst bez wyjaśnienia i bez następnego kroku.

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. EmptyTitle renderuje <div>. Gdy stan pusty zajmuje całą stronę lub sekcję, umieść w nim nagłówek odpowiedniego poziomu.
  • Ikony są dekoracyjne. Znaczenie niesie tytuł; ikony w EmptyMedia nie 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.

PropTypDomyślnie
variant

Wartość outline dodaje przerywaną ramkę wokół obszarów, które czekają na treść.

"default" | "outline""default"

EmptyMedia

PropTypDomyślnie
variant

Wartość icon umieszcza ikonę na wyniesionym kafelku o boku 44 px. Wartość default zostawia slot bez stylów — na ilustracje.

"default" | "icon""default"

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.