ATOM
Components (registry)Layout

Empty

A calm blank state with icon, message, and optional CTA. Zero results and first-run screens.

Preview

Variants: defaultoutlinefilled (default: default)

Editorial

<!-- F12c editorial — non-derivable only. Review: Karen. -->

## Ejemplos

Zero results with recovery CTA:

```tsx import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent, } from '@/components/atoms/Empty';

<Empty variant="outline"> <EmptyHeader> <EmptyMedia variant="icon">{/* icon */}</EmptyMedia> <EmptyTitle>No projects</EmptyTitle> <EmptyDescription>Create one to get started.</EmptyDescription> </EmptyHeader> <EmptyContent><button type="button">New project</button></EmptyContent> </Empty> ```

## Accesibilidad

- Title should explain the empty state; put recovery actions in `EmptyContent`. - Decorative media can be `aria-hidden` when the title already carries the meaning.

## Cuándo no usar

- Loading placeholders → `Skeleton` / `Spinner`. - Error failures with retry of a failed request → error banner/toast + retry, not a first-run empty.

## Criterio de uso

- Usa Empty cuando el estado es válido pero todavía no hay contenido: primer uso, cero resultados o una colección que quedó vacía. - Explica qué está vacío y ofrece una acción de recuperación o creación cuando exista; el CTA debe estar en `EmptyContent`. - Distingue “no hay datos” de “no pudimos cargar”: el segundo caso necesita error y retry, no un mensaje neutro.

## Gotchas

- El título debe nombrar el estado, no sólo decir “sin resultados”; la descripción puede explicar el siguiente paso. - La media decorativa no debe competir con el mensaje ni repetirlo para tecnologías asistivas.

Uso

import {
  Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent,
} from '@/components/atoms/Empty';

<Empty variant="outline">
  <EmptyHeader>
    <EmptyMedia variant="icon">{/* icon */}</EmptyMedia>
    <EmptyTitle>No projects</EmptyTitle>
    <EmptyDescription>Create one to get started.</EmptyDescription>
  </EmptyHeader>
  <EmptyContent><button type="button">New project</button></EmptyContent>
</Empty>

Props

PropTipoDefaultRango / opcionesWhatHow
variantselectdefault`default`, `outline`, `filled`Surface style of the empty state container.default for page empties; outline inside cards; filled for soft callouts. Default: default.

Gotchas

  • react

    Compose EmptyHeader/Media/Title/Description/Content. Media has its own variant (default|icon).

  • a11y

    Title should explain the empty state; put recovery actions in EmptyContent.

Anatomía CSS

<div class="empty">
  <span class="empty__content"></span>
  <span class="empty__description"></span>
  <span class="empty__header"></span>
  <span class="empty__media"></span>
  <span class="empty__media--icon"></span>
  <span class="empty__title"></span>
</div>
ClasePropósito
emptyroot
empty--filledmodifier
empty--outlinemodifier
empty__contentelement
empty__descriptionelement
empty__headerelement
empty__mediaelement
empty__media--iconelement
empty__titleelement

Tokens resueltos

Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.

VariantePropValorToken
outlineborder1pxstroke.hairline
filledbg#fafafabackground
allfg#525252muted.foreground
allbg#f5f5f5muted

CSS mínimo funcional

Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).

/* -------------------------------------------------------------------------
   Empty

   Empty state placeholder. Centered layout with media, title,
   description, and action slot.

   Variants: outline (border), filled (bg)
   Parts:    .empty, .empty__header, .empty__media, .empty__title,
             .empty__description, .empty__content
   ------------------------------------------------------------------------- */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  width: 100%;
  padding: 48px 24px;
  font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
  text-align: center;
}

/* ---- Variants ---- */

.empty--outline {
  border: 1px dashed #e5e5e5;
  border-radius: 12px;
}

.empty--filled {
  background-color: #f5f5f5;
  border-radius: 12px;
}

/* ---- Header ---- */

.empty__header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  max-width: 420px;
}

/* ---- Media ---- */

.empty__media {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #525252;
}

.empty__media--icon {
  width: 48px;
  height: 48px;
  border-radius: 12px;
  background-color: #f5f5f5;
  padding: 12px;
}

.empty--filled .empty__media--icon {
  background-color: #fafafa;
}

.empty__media svg {
  width: 100%;
  height: 100%;
}

/* ---- Title ---- */

.empty__title {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.5;
  color: #0a0a0a;
  margin: 0;
}

/* ---- Description ---- */

.empty__description {
  font-size: 12.8px;
  line-height: 1.45;
  color: #525252;
  margin: 0;
}

/* ---- Content (actions slot) ---- */

.empty__content {
  display: flex;
  align-items: center;
  gap: 12px;
}

Codigo fuente

components-react
components/atoms/Empty.tsx
import { type ReactNode } from 'react';

function cn(...classes: (string | false | undefined | null)[]) {
  return classes.filter(Boolean).join(' ');
}

/* ---- Root ---- */

export type EmptyProps = {
  variant?: 'default' | 'outline' | 'filled';
  children: ReactNode;
  className?: string;
};

export function Empty({ variant = 'default', children, className }: EmptyProps) {
  return (
    <div
      className={cn(
        'empty',
        variant !== 'default' && `empty--${variant}`,
        className,
      )}
    >
      {children}
    </div>
  );
}

/* ---- Header ---- */

export function EmptyHeader({ children, className }: { children: ReactNode; className?: string }) {
  return <div className={cn('empty__header', className)}>{children}</div>;
}

/* ---- Media ---- */

export type EmptyMediaProps = {
  variant?: 'default' | 'icon';
  children: ReactNode;
  className?: string;
};

export function EmptyMedia({ variant = 'default', children, className }: EmptyMediaProps) {
  return (
    <div className={cn('empty__media', variant === 'icon' && 'empty__media--icon', className)}>
      {children}
    </div>
  );
}

/* ---- Title ---- */

export function EmptyTitle({ children, className }: { children: ReactNode; className?: string }) {
  return <h3 className={cn('empty__title', className)}>{children}</h3>;
}

/* ---- Description ---- */

export function EmptyDescription({ children, className }: { children: ReactNode; className?: string }) {
  return <p className={cn('empty__description', className)}>{children}</p>;
}

/* ---- Content (actions) ---- */

export function EmptyContent({ children, className }: { children: ReactNode; className?: string }) {
  return <div className={cn('empty__content', className)}>{children}</div>;
}

Webflow

Webflowempty

Pega en el Designer como application/json, luego convierte a Component (Atom / Empty) y publica. Formato interno no documentado de Webflow — regenerable, no dependencia de runtime.

Setup del sitio (una vez)

Custom Code → Head:

<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/tokens.css">
<link rel="stylesheet" href="https://atom-web-ds.vercel.app/v1/components.css">

Unsupported (no silencioso)

  • selector on .empty--filled .empty__media--iconcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .empty__media svgcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)

Tras pegar: Create component → nombre Atom / Empty → Publish.

Componentes relacionados

On this page

Detalles

Publicado14 de mayo de 2026
Categorialayout
Lectura...
Visitas...
Ayuda?Slack

Componente

Empty

Source

components-react / cssDisponible via MCP: atom_uikit_source("empty")
Abrir en Storybook