Zum Inhalt springen

Leer

Ein Platzhalter für Suchanfragen ohne Ergebnisse, leere Dashboards und Startbildschirme – Symbol, Titel und Aktion, eingerahmt von neobrutalistischen Rahmen.

import { IconFolderCode } from "@tabler/icons-react"
import { ArrowUpRightIcon } from "lucide-react"

Der Empty State füllt die Lücke, wenn es nichts zu rendern gibt – ein Slot für ein Icon oder einen Avatar, ein Titel, eine Beschreibung und ein Call-to-Action. Er besteht aus komponierbaren divs ohne Abhängigkeiten (kein Primitive darunter), nach dem neobrutalistischen Rezept gestaltet: dicke Rahmen und kräftige Überschriften-Typografie.

Typische Anwendungsfälle:

  • Suchen und Filter ohne Treffer – erkläre, warum die Liste leer ist, und biete einen Weg an, die Abfrage zurückzusetzen oder zu erweitern.
  • First-Run-Screens – „Noch keine Projekte“ mit einem Erstellen-Button, der direkt auf den nächsten Schritt zeigt.
  • Geleerte Posteingänge und Warteschlangen – mach deutlich, dass die leere Liste ein Erfolg ist und kein Fehler.

Installation

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/empty.json
npx shadcn@latest add https://neobrutalism.com/r/base/empty.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/empty.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/empty.json

Kopiere den folgenden Code und füge ihn in dein Projekt ein.

components/ui/empty.tsx
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

function Empty({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty"
      className={cn(
        "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded border-2 bg-card p-6 text-center text-balance",
        className
      )}
      {...props}
    />
  )
}

function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty-header"
      className={cn("flex max-w-sm flex-col items-center gap-2", className)}
      {...props}
    />
  )
}

const emptyMediaVariants = cva(
  "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default: "bg-transparent",
        icon: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-4",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

function EmptyMedia({
  className,
  variant = "default",
  ...props
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
  return (
    <div
      data-slot="empty-icon"
      data-variant={variant}
      className={cn(emptyMediaVariants({ variant, className }))}
      {...props}
    />
  )
}

function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty-title"
      className={cn(
        "cn-font-heading text-sm font-medium tracking-tight",
        className
      )}
      {...props}
    />
  )
}

function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
  return (
    <div
      data-slot="empty-description"
      className={cn(
        "text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
        className
      )}
      {...props}
    />
  )
}

function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="empty-content"
      className={cn(
        "flex w-full max-w-sm min-w-0 flex-col items-center gap-2.5 text-sm text-balance",
        className
      )}
      {...props}
    />
  )
}

export {
  Empty,
  EmptyHeader,
  EmptyTitle,
  EmptyDescription,
  EmptyContent,
  EmptyMedia,
}

Passe die Importpfade an deine Projektkonfiguration an.

Verwendung

import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty"
<Empty>
  <EmptyHeader>
    <EmptyMedia variant="icon">
      <Icon />
    </EmptyMedia>
    <EmptyTitle>No data</EmptyTitle>
    <EmptyDescription>No data found</EmptyDescription>
  </EmptyHeader>
  <EmptyContent>
    <Button>Add data</Button>
  </EmptyContent>
</Empty>

Komposition

Verwende die folgende Komposition, um einen Empty-Zustand zu erstellen:

Empty
├── EmptyHeader
│   ├── EmptyMedia
│   ├── EmptyTitle
│   └── EmptyDescription
└── EmptyContent

Beispiele

Umriss

Verwende die Utility-Klasse border, um einen leeren Zustand mit Umriss zu erstellen.

import { IconCloud } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"

Hintergrund

Verwende die Utilities bg-* und bg-gradient-*, um dem leeren Zustand einen Hintergrund hinzuzufügen.

import { IconBell } from "@tabler/icons-react"
import { RefreshCcwIcon } from "lucide-react"

Avatar

Verwende die Komponente EmptyMedia, um im leeren Zustand einen Avatar anzuzeigen.

import {
  Avatar,
  AvatarFallback,

Avatar-Gruppe

Verwende die Komponente EmptyMedia, um im leeren Zustand eine Avatar-Gruppe anzuzeigen.

import { PlusIcon } from "lucide-react"

import {

InputGroup

Du kannst der EmptyContent-Komponente eine InputGroup-Komponente hinzufügen.

import { SearchIcon } from "lucide-react"

import {

RTL

Um RTL-Unterstützung in Neobrutalism zu aktivieren, siehe die RTL-Konfigurationsanleitung.

"use client"

import * as React from "react"

Barrierefreiheit

Der Empty State ist rein präsentational – schlichte divs ohne Rollen und ohne Tastaturverhalten, es gibt also nichts zu verdrahten. Kennzeichne ein rein dekoratives EmptyMedia-Icon mit aria-hidden="true" und setze Aktionen in EmptyContent als echte Button- oder Link-Elemente um, damit sie fokussierbar bleiben. Ersetzt der Empty State Inhalte nach einer asynchronen Operation (eine Suche kommt ohne Treffer zurück), kündige ihn über eine Live-Region an – der DOM-Austausch selbst bleibt für Screenreader stumm.

API-Referenz

Empty

Die Hauptkomponente des leeren Zustands. Umschließt die Komponenten EmptyHeader und EmptyContent.

PropTypeDefault
classNamestring
<Empty>
  <EmptyHeader />
  <EmptyContent />
</Empty>

EmptyHeader

Die Komponente EmptyHeader umschließt Medium, Titel und Beschreibung des leeren Zustands.

PropTypeDefault
classNamestring
<EmptyHeader>
  <EmptyMedia />
  <EmptyTitle />
  <EmptyDescription />
</EmptyHeader>

EmptyMedia

Verwende die Komponente EmptyMedia, um das Medium des leeren Zustands anzuzeigen, etwa ein Icon oder ein Bild. Du kannst sie auch verwenden, um andere Komponenten wie einen Avatar anzuzeigen.

PropTypeDefault
variant"default" | "icon"default
classNamestring
<EmptyMedia variant="icon">
  <Icon />
</EmptyMedia>
<EmptyMedia>
  <Avatar>
    <AvatarImage src="..." />
    <AvatarFallback>CN</AvatarFallback>
  </Avatar>
</EmptyMedia>

EmptyTitle

Verwende die Komponente EmptyTitle, um den Titel des leeren Zustands anzuzeigen.

PropTypeDefault
classNamestring
<EmptyTitle>No data</EmptyTitle>

EmptyDescription

Verwende die Komponente EmptyDescription, um die Beschreibung des leeren Zustands anzuzeigen.

PropTypeDefault
classNamestring
<EmptyDescription>You do not have any notifications.</EmptyDescription>

EmptyContent

Verwende die Komponente EmptyContent, um den Inhalt des leeren Zustands anzuzeigen, etwa einen Button, eine Eingabe oder einen Link.

PropTypeDefault
classNamestring
<EmptyContent>
  <Button>Add Project</Button>
</EmptyContent>