Skip to content

Empty State

A centered placeholder with an icon, title, and hint shown when a list or view has no content.

A centered placeholder with an icon, title, and hint shown when a list or view has no content. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.

Root class newt-empty-state
import {
  EmptyState,
  EmptyStateDescription,
  EmptyStateIcon,
  EmptyStateTitle,
} from "@/registry/bases/newt/ui/empty-state"

export default function EmptyStateDemo() {
  return (
    <EmptyState>
      <EmptyStateIcon>📭</EmptyStateIcon>
      <EmptyStateTitle>Nothing here yet</EmptyStateTitle>
      <EmptyStateDescription>
        Description of what to do next.
      </EmptyStateDescription>
    </EmptyState>
  )
}
<script setup lang="ts">
import {
  EmptyState,
  EmptyStateDescription,
  EmptyStateIcon,
  EmptyStateTitle,
} from "~~/registry/bases/newt/ui/empty-state"
</script>

<template>
  <EmptyState>
    <EmptyStateIcon>📭</EmptyStateIcon>
    <EmptyStateTitle>Nothing here yet</EmptyStateTitle>
    <EmptyStateDescription
      >Description of what to do next.</EmptyStateDescription
    >
  </EmptyState>
</template>

Installation

pnpm dlx newtui@latest add empty-state
npx newtui@latest add empty-state
yarn dlx newtui@latest add empty-state
bunx newtui@latest add empty-state

Copy the source into your project

import * as React from "react"

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

const EmptyState = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn(
      "flex flex-col items-center gap-2 px-5 py-10 text-center text-newt-text-muted",
      className
    )}
    {...props}
  />
))
EmptyState.displayName = "EmptyState"

const EmptyStateIcon = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    aria-hidden="true"
    className={cn("mb-1 text-[40px] leading-none opacity-60", className)}
    {...props}
  />
))
EmptyStateIcon.displayName = "EmptyStateIcon"

const EmptyStateTitle = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("text-base font-semibold text-newt-text-primary", className)}
    {...props}
  />
))
EmptyStateTitle.displayName = "EmptyStateTitle"

const EmptyStateDescription = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("max-w-[320px] text-sm leading-normal", className)}
    {...props}
  />
))
EmptyStateDescription.displayName = "EmptyStateDescription"

export { EmptyState, EmptyStateIcon, EmptyStateTitle, EmptyStateDescription }
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

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

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div
    :class="
      cn(
        'flex flex-col items-center gap-2 px-5 py-10 text-center text-newt-text-muted',
        props.class
      )
    "
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

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

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div
    aria-hidden="true"
    :class="cn('mb-1 text-[40px] leading-none opacity-60', props.class)"
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

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

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div
    :class="cn('text-base font-semibold text-newt-text-primary', props.class)"
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

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

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div :class="cn('max-w-[320px] text-sm leading-normal', props.class)">
    <slot />
  </div>
</template>
export { default as EmptyState } from "./EmptyState.vue"
export { default as EmptyStateIcon } from "./EmptyStateIcon.vue"
export { default as EmptyStateTitle } from "./EmptyStateTitle.vue"
export { default as EmptyStateDescription } from "./EmptyStateDescription.vue"

Update the import paths

The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.

Usage

import { EmptyState } from "@/components/ui/empty-state"

export function Example() {
  return <EmptyState />
}
<script setup lang="ts">
import { EmptyState, EmptyStateIcon, EmptyStateTitle, EmptyStateDescription } from "@/components/ui/empty-state"
</script>

<template>
  <EmptyState />
</template>

API reference

EmptyState

PropType
children / default slot?React.ReactNode / Vue slot

Content rendered inside the component.

TypeReact.ReactNode / Vue slot
className / class?string

Additional classes merged with the component defaults.

Typestring

The registry exports EmptyState, EmptyStateIcon, EmptyStateTitle, EmptyStateDescription. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.

Accessibility

Preserve the native semantics of the rendered elements and pass through ARIA attributes when the surrounding context needs a label or description. Any interactive child needs a visible focus indicator, and meaning must not rely on colour alone.

Tokens

Empty State reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-empty-state.