Empty State

What to show when there is nothing to show

Empty State fills the space where content will be: a muted icon, a title, a line of explanation and the action that fixes it. Use it for first runs, empty lists and searches with no results.

Installation

Install the dependencies.

npm install @kushagradhawan/kookie-ui

Copy these files into components/blocks/empty-state. Keep the folder names, since blocks import each other by relative path.

components/blocks/empty-state/empty-state.tsx
import * as React from 'react';
import { Box, Flex, Heading, Text } from '@kushagradhawan/kookie-ui';
import type { BoxProps, FlexProps, HeadingProps, TextProps } from '@kushagradhawan/kookie-ui';
import './empty-state.css';

type EmptyStateRootProps = Omit<FlexProps, 'direction' | 'align'> & {
  /** @default "center" */
  align?: 'start' | 'center';
};

const EmptyStateRoot = React.forwardRef<HTMLDivElement, EmptyStateRootProps>(
  ({ align = 'center', gap = '4', ...props }, ref) => <Flex ref={ref} direction="column" align={align} gap={gap} {...props} />,
);
EmptyStateRoot.displayName = 'EmptyState.Root';

/** Sizes and tints the icon or illustration inside it. */
const EmptyStateIcon = React.forwardRef<HTMLDivElement, BoxProps>(({ className, ...props }, ref) => (
  <Box ref={ref} className={className ? `empty-state-icon ${className}` : 'empty-state-icon'} {...props} />
));
EmptyStateIcon.displayName = 'EmptyState.Icon';

/** Groups the title and description. */
const EmptyStateContent = React.forwardRef<HTMLDivElement, FlexProps>(({ direction = 'column', gap = '1', ...props }, ref) => (
  <Flex ref={ref} direction={direction} gap={gap} {...props} />
));
EmptyStateContent.displayName = 'EmptyState.Content';

const EmptyStateTitle = React.forwardRef<HTMLHeadingElement, HeadingProps>(
  ({ as = 'h3', size = '5', weight = 'medium', align = 'center', ...props }, ref) => (
    <Heading ref={ref} as={as} size={size} weight={weight} align={align} {...props} />
  ),
);
EmptyStateTitle.displayName = 'EmptyState.Title';

const EmptyStateDescription = React.forwardRef<HTMLParagraphElement, TextProps>(
  ({ size = '2', emphasis = 'medium', align = 'center', ...props }, ref) => (
    <Text ref={ref} size={size} emphasis={emphasis} align={align} {...props} />
  ),
);
EmptyStateDescription.displayName = 'EmptyState.Description';

const EmptyStateActions = React.forwardRef<HTMLDivElement, FlexProps>(
  ({ gap = '2', align = 'center', justify = 'center', ...props }, ref) => (
    <Flex ref={ref} gap={gap} align={align} justify={justify} {...props} />
  ),
);
EmptyStateActions.displayName = 'EmptyState.Actions';

export const EmptyState = Object.assign(EmptyStateRoot, {
  Root: EmptyStateRoot,
  Icon: EmptyStateIcon,
  Content: EmptyStateContent,
  Title: EmptyStateTitle,
  Description: EmptyStateDescription,
  Actions: EmptyStateActions,
});
components/blocks/empty-state/empty-state.css
/* Override --empty-state-icon-size on EmptyState.Icon to resize the icon. */
.empty-state-icon {
  --empty-state-icon-size: var(--space-6);
  color: var(--gray-a9);
}

.empty-state-icon :where(svg) {
  width: var(--empty-state-icon-size);
  height: var(--empty-state-icon-size);
}

Usage

import { EmptyState } from '@/components/blocks/empty-state/empty-state';
 
<EmptyState.Root>
  <EmptyState.Icon>
    <HugeiconsIcon icon={Folder01Icon} strokeWidth={1.75} />
  </EmptyState.Icon>
  <EmptyState.Content>
    <EmptyState.Title>No projects yet</EmptyState.Title>
    <EmptyState.Description>Projects you create or join show up here.</EmptyState.Description>
  </EmptyState.Content>
  <EmptyState.Actions>
    <Button variant="classic">New project</Button>
  </EmptyState.Actions>
</EmptyState.Root>

Anatomy

<EmptyState.Root>
  <EmptyState.Icon />
  <EmptyState.Content>
    <EmptyState.Title />
    <EmptyState.Description />
  </EmptyState.Content>
  <EmptyState.Actions />
</EmptyState.Root>

Props

Every part accepts the props of the component it wraps. Only the additions and changed defaults are listed.

PartWrapsAdds or changes
RootFlexalign?: 'start' | 'center' ('center'). Always a column, gap="4"
IconBoxSizes a child svg to --empty-state-icon-size and tints it --gray-a9
ContentFlexdirection="column", gap="1"
TitleHeadingas="h3", size="5", weight="medium", align="center"
DescriptionTextsize="2", emphasis="medium", align="center"
ActionsFlexgap="2", align="center", justify="center"

Icon Size

The icon is var(--space-6) square. Override the variable to change it.

<EmptyState.Icon style={{ '--empty-state-icon-size': 'var(--space-8)' } as React.CSSProperties}>
  <HugeiconsIcon icon={Folder01Icon} strokeWidth={1.75} />
</EmptyState.Icon>

Examples

No Search Results

No results for “invoice”

Check the spelling or try a broader term.
<EmptyState.Root>
  <EmptyState.Icon>
    <HugeiconsIcon icon={Search01Icon} strokeWidth={1.75} />
  </EmptyState.Icon>
  <EmptyState.Content>
    <EmptyState.Title>No results for “{query}”</EmptyState.Title>
    <EmptyState.Description>
      Check the spelling or try a broader term.
    </EmptyState.Description>
  </EmptyState.Content>
  <EmptyState.Actions>
    <Button variant="soft" color="gray" size="2" onClick={clearSearch}>
      Clear search
    </Button>
  </EmptyState.Actions>
</EmptyState.Root>

First Project

No projects yet

Projects you create or join show up here.
<EmptyState.Root>
  <EmptyState.Icon>
    <HugeiconsIcon icon={Folder01Icon} strokeWidth={1.75} />
  </EmptyState.Icon>
  <EmptyState.Content>
    <EmptyState.Title>No projects yet</EmptyState.Title>
    <EmptyState.Description>
      Projects you create or join show up here.
    </EmptyState.Description>
  </EmptyState.Content>
  <EmptyState.Actions>
    <Button variant="soft" color="gray" size="2">Import</Button>
    <Button variant="classic" size="2">New project</Button>
  </EmptyState.Actions>
</EmptyState.Root>

Accessibility

The icon is decorative; the title and description carry the meaning. Title is an h3; change as to fit the page's heading order. When an empty state replaces search results, announce it by placing the results area in an aria-live="polite" region.

© 2026 Kushagra Dhawan. Licensed under MIT. GitHub.

Theme

Accent color

Gray color

Appearance

Radius

Scaling

Panel background