Empty

GitHub
A component to display an empty state.

Usage

Use the Empty component to display a placeholder state when there is no content to show.

No projects found

It looks like you haven't added any projects. Create one to get started.

Title Soon

Use the title prop to set the title of the empty state.

No projects found

<template>
  <PEmpty title="No projects found" />
</template>
The title is rendered as an h2 by default. Use the as prop with an object to change it, for example :as="{ title: 'h3' }". You can also set the root element this way: :as="{ root: 'section', title: 'h3' }".

Description

Use the description prop to set the description of the empty state.

No projects found

It looks like you haven't added any projects. Create one to get started.
<template>
  <PEmpty
    title="No projects found"
    description="It looks like you haven't added any projects. Create one to get started."
  />
</template>

Icon

Use the icon prop to set the icon of the empty state.

No projects found

It looks like you haven't added any projects. Create one to get started.
<template>
  <PEmpty
    icon="i-lucide-file"
    title="No projects found"
    description="It looks like you haven't added any projects. Create one to get started."
  />
</template>

Avatar

Use the avatar prop to set the avatar of the empty state.

No projects found

It looks like you haven't added any projects. Create one to get started.
<template>
  <PEmpty
    :avatar="{
      src: 'https://github.com/nuxt.png'
    }"
    title="No projects found"
    description="It looks like you haven't added any projects. Create one to get started."
  />
</template>

Loading

Use the loading prop to show a loading icon in place of the icon. The layout stays identical, so you can toggle between loading and empty states without layout shifts.

Loading projects

Please wait while we fetch your projects.
<template>
  <PEmpty
    icon="i-lucide-file"
    loading
    title="Loading projects"
    description="Please wait while we fetch your projects."
  />
</template>

Loading Icon

Use the loading-icon prop to customize the loading icon. Defaults to i-lucide-loader-circle.

Loading projects

Please wait while we fetch your projects.
<template>
  <PEmpty
    icon="i-lucide-file"
    loading
    loading-icon="i-lucide-loader"
    title="Loading projects"
    description="Please wait while we fetch your projects."
  />
</template>
You can customize this icon globally in your app.config.ts under ui.icons.loading key.
You can customize this icon globally in your vite.config.ts under ui.icons.loading key.

Actions

Use the actions prop to add some Button actions to the empty state.

No projects found

It looks like you haven't added any projects. Create one to get started.
<template>
  <PEmpty
    icon="i-lucide-file"
    title="No projects found"
    description="It looks like you haven't added any projects. Create one to get started."
    :actions="[
      {
        icon: 'i-lucide-plus',
        label: 'Create new'
      },
      {
        icon: 'i-lucide-refresh-cw',
        label: 'Refresh',
        color: 'neutral',
        variant: 'subtle'
      }
    ]"
  />
</template>

Variant

Use the variant prop to change the variant of the empty state.

No notifications

You're all caught up. New notifications will appear here.
<template>
  <PEmpty
    variant="naked"
    icon="i-lucide-bell"
    title="No notifications"
    description="You're all caught up. New notifications will appear here."
    :actions="[
      {
        icon: 'i-lucide-refresh-cw',
        label: 'Refresh',
        color: 'neutral',
        variant: 'subtle'
      }
    ]"
  />
</template>

Size

Use the size prop to change the size of the empty state.

No notifications

You're all caught up. New notifications will appear here.
<template>
  <PEmpty
    size="xl"
    icon="i-lucide-bell"
    title="No notifications"
    description="You're all caught up. New notifications will appear here."
    :actions="[
      {
        icon: 'i-lucide-refresh-cw',
        label: 'Refresh',
        color: 'neutral',
        variant: 'subtle'
      }
    ]"
  />
</template>

Examples

With slots

Use the available slots to create a more complex empty state.

UnjsNuxt

No team members

Invite your team to collaborate on this project.
danielroe

Daniel Roe

danielroe

pi0

Pooya Parsa

pi0

atinux

Sébastien Chopin

atinux

benjamincanac

Benjamin Canac

benjamincanac

<script setup lang="ts">
import type { UserProps } from 'pohon-ui';

const members: Array<UserProps> = [
  {
    name: 'Daniel Roe',
    description: 'danielroe',
    to: 'https://github.com/danielroe',
    target: '_blank',
    avatar: {
      src: 'https://github.com/danielroe.png',
      alt: 'danielroe',
      loading: 'lazy' as const,
    },
  },
  {
    name: 'Pooya Parsa',
    description: 'pi0',
    to: 'https://github.com/pi0',
    target: '_blank',
    avatar: {
      src: 'https://github.com/pi0.png',
      alt: 'pi0',
      loading: 'lazy' as const,
    },
  },
  {
    name: 'Sébastien Chopin',
    description: 'atinux',
    to: 'https://github.com/atinux',
    target: '_blank',
    avatar: {
      src: 'https://github.com/atinux.png',
      alt: 'atinux',
      loading: 'lazy' as const,
    },
  },
  {
    name: 'Benjamin Canac',
    description: 'benjamincanac',
    to: 'https://github.com/benjamincanac',
    target: '_blank',
    avatar: {
      src: 'https://github.com/benjamincanac.png',
      alt: 'benjamincanac',
      loading: 'lazy' as const,
    },
  },
];
</script>

<template>
  <PEmpty
    title="No team members"
    description="Invite your team to collaborate on this project."
    variant="naked"
    :actions="[{
      label: 'Invite members',
      icon: 'i-lucide-user-plus',
      color: 'neutral',
    }]"
  >
    <template #leading>
      <PAvatarGroup size="xl">
        <PAvatar
          src="https://github.com/nuxt.png"
          alt="Nuxt"
          loading="lazy"
        />
        <PAvatar
          src="https://github.com/unjs.png"
          alt="Unjs"
          loading="lazy"
        />
      </PAvatarGroup>
    </template>

    <template #footer>
      <PSeparator class="my-4" />

      <div class="gap-4 grid grid-cols-2">
        <PPageCard
          v-for="(member, index) in members"
          :key="index"
          :to="member.to"
          :ui="{ container: 'sm:p-4' }"
        >
          <PUser
            :avatar="member.avatar"
            :name="member.name"
            :description="member.description"
            :ui="{ name: 'truncate' }"
          />
        </PPageCard>
      </div>
    </template>
  </PEmpty>
</template>

API

Props

Prop Default Type
as
'div'
any

The element or component this component should render as.

icon
any

The icon displayed above the title.

avatar
AvatarProps
loading
boolean

When true, the loading icon will be displayed.

loadingIcon
appConfig.ui.icons.loading
any

The icon when the loading prop is true.

title
string
description
string
actions
ButtonProps[]

Display a list of Button in the body.

variant
'outline'
"outline" | "solid" | "soft" | "subtle" | "naked"
size
'md'
"md" | "xs" | "sm" | "lg" | "xl"
ui
{ root?: SlotClass; header?: SlotClass; avatar?: SlotClass; title?: SlotClass; description?: SlotClass; body?: SlotClass; actions?: SlotClass; footer?: SlotClass; }

Slots

Slot Type
header
{}
leading
{ ui: object; }
title
{}
description
{}
body
{}
actions
{}
footer
{}

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    empty: {
      slots: {
        root: 'relative flex flex-col items-center justify-center gap-4 rounded-lg p-4 sm:p-6 lg:p-8 min-w-0',
        header: 'flex flex-col items-center gap-2 max-w-sm text-center',
        avatar: 'shrink-0 mb-2',
        title: 'color-text-highlighted text-pretty font-500',
        description: 'text-balance text-center',
        body: 'flex flex-col items-center gap-4 max-w-sm',
        actions: 'flex flex-wrap justify-center gap-2 shrink-0',
        footer: 'flex flex-col items-center gap-2 max-w-sm'
      },
      variants: {
        size: {
          xs: {
            avatar: 'pohon:size-8 text-base',
            title: 'text-sm',
            description: 'text-xs'
          },
          sm: {
            avatar: 'pohon:size-9 text-lg',
            title: 'text-sm',
            description: 'text-xs'
          },
          md: {
            avatar: 'pohon:size-10 text-xl',
            title: 'text-base',
            description: 'text-sm'
          },
          lg: {
            avatar: 'pohon:size-11 text-[22px]',
            title: 'text-base',
            description: 'text-sm'
          },
          xl: {
            avatar: 'pohon:size-12 text-2xl',
            title: 'text-lg',
            description: 'text-base'
          }
        },
        variant: {
          solid: {
            root: 'bg-background-inverted',
            title: 'color-text-inverted',
            description: 'color-text-dimmed'
          },
          outline: {
            root: 'bg-background ring ring-border',
            description: 'color-text-muted'
          },
          soft: {
            root: 'bg-background-elevated/50',
            description: 'color-text-toned'
          },
          subtle: {
            root: 'bg-background-elevated/50 ring ring-border',
            description: 'color-text-toned'
          },
          naked: {
            description: 'color-text-muted'
          }
        },
        loading: {
          true: {
            avatar: '[&>[data-slot=icon]]:animate-spin'
          }
        }
      },
      defaultVariants: {
        variant: 'outline',
        size: 'md'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from 'pohon-ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        empty: {
          slots: {
            root: 'relative flex flex-col items-center justify-center gap-4 rounded-lg p-4 sm:p-6 lg:p-8 min-w-0',
            header: 'flex flex-col items-center gap-2 max-w-sm text-center',
            avatar: 'shrink-0 mb-2',
            title: 'color-text-highlighted text-pretty font-500',
            description: 'text-balance text-center',
            body: 'flex flex-col items-center gap-4 max-w-sm',
            actions: 'flex flex-wrap justify-center gap-2 shrink-0',
            footer: 'flex flex-col items-center gap-2 max-w-sm'
          },
          variants: {
            size: {
              xs: {
                avatar: 'pohon:size-8 text-base',
                title: 'text-sm',
                description: 'text-xs'
              },
              sm: {
                avatar: 'pohon:size-9 text-lg',
                title: 'text-sm',
                description: 'text-xs'
              },
              md: {
                avatar: 'pohon:size-10 text-xl',
                title: 'text-base',
                description: 'text-sm'
              },
              lg: {
                avatar: 'pohon:size-11 text-[22px]',
                title: 'text-base',
                description: 'text-sm'
              },
              xl: {
                avatar: 'pohon:size-12 text-2xl',
                title: 'text-lg',
                description: 'text-base'
              }
            },
            variant: {
              solid: {
                root: 'bg-background-inverted',
                title: 'color-text-inverted',
                description: 'color-text-dimmed'
              },
              outline: {
                root: 'bg-background ring ring-border',
                description: 'color-text-muted'
              },
              soft: {
                root: 'bg-background-elevated/50',
                description: 'color-text-toned'
              },
              subtle: {
                root: 'bg-background-elevated/50 ring ring-border',
                description: 'color-text-toned'
              },
              naked: {
                description: 'color-text-muted'
              }
            },
            loading: {
              true: {
                avatar: '[&>[data-slot=icon]]:animate-spin'
              }
            }
          },
          defaultVariants: {
            variant: 'outline',
            size: 'md'
          }
        }
      }
    })
  ]
})

Changelog

No recent changes