Aller au contenu

Vide

Élément de remplacement pour les recherches sans résultat, les tableaux de bord vides et les écrans de premier lancement : icône, titre et action encadrés par des bordures de style néobrutaliste.

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

L’état vide comble le manque quand il n’y a rien à afficher — un emplacement pour une icône ou un avatar, un titre, une description et un appel à l’action. C’est un ensemble de div composables et sans dépendance (aucune primitive sous-jacente), habillé selon la recette néobrutaliste : bordures épaisses et titres à la typographie affirmée.

À privilégier pour :

  • Recherches et filtres sans résultat — expliquez pourquoi la liste est vide et proposez un moyen de réinitialiser ou d’élargir la requête.
  • Écrans de première utilisation — « Aucun projet pour l’instant » avec un bouton de création pointant vers la toute prochaine étape.
  • Boîtes de réception et files d’attente vidées — confirmez que la liste vide est une réussite, pas une erreur.

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

Copiez-collez le code suivant dans votre projet.

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,
}

Adaptez les chemins d’import à la structure de votre projet.

Utilisation

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>

Composition

Utilisez la composition suivante pour construire un état Empty :

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

Exemples

Contour

Utilisez la classe utilitaire border pour créer un état vide avec contour.

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

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

Arrière-plan

Utilisez les utilitaires bg-* et bg-gradient-* pour ajouter un arrière-plan à l’état vide.

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

Avatar

Utilisez le composant EmptyMedia pour afficher un avatar dans l’état vide.

import {
  Avatar,
  AvatarFallback,

Groupe d’avatars

Utilisez le composant EmptyMedia pour afficher un groupe d’avatars dans l’état vide.

import { PlusIcon } from "lucide-react"

import {

InputGroup

Vous pouvez ajouter un composant InputGroup au composant EmptyContent.

import { SearchIcon } from "lucide-react"

import {

RTL

Pour activer le RTL dans Neobrutalism, consultez le guide de configuration RTL.

"use client"

import * as React from "react"

Accessibilité

L’état vide est purement présentationnel — de simples div sans rôle ni comportement clavier, il n’y a donc rien à câbler. Marquez une icône EmptyMedia purement décorative avec aria-hidden="true", et gardez les actions de EmptyContent sous forme de vrais Button ou de vrais liens afin qu’elles restent focalisables. Quand l’état vide remplace du contenu après une opération asynchrone (une recherche qui revient sans résultat), annoncez-le via une région live — le remplacement dans le DOM est en soi silencieux pour les lecteurs d’écran.

Référence API

Empty

Le composant principal de l’état vide. Il enveloppe les composants EmptyHeader et EmptyContent.

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

EmptyHeader

Le composant EmptyHeader enveloppe le média, le titre et la description de l’état vide.

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

EmptyMedia

Utilisez le composant EmptyMedia pour afficher le média de l’état vide, comme une icône ou une image. Vous pouvez aussi l’utiliser pour afficher d’autres composants, comme un avatar.

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

EmptyTitle

Utilisez le composant EmptyTitle pour afficher le titre de l’état vide.

PropTypeDefault
classNamestring
<EmptyTitle>No data</EmptyTitle>

EmptyDescription

Utilisez le composant EmptyDescription pour afficher la description de l’état vide.

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

EmptyContent

Utilisez le composant EmptyContent pour afficher le contenu de l’état vide, comme un bouton, un champ de saisie ou un lien.

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