ATOM

Avatar

Shows a face, initials, or fallback icon at a fixed size. People in lists, comments, and headers.

Preview

Sizes: xssml (default: s)

Editorial

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

## Ejemplos

Person in a list:

```tsx import { Avatar } from '@/components/atoms/Avatar';

<Avatar type="image-border" size="m" src="/u/ada.jpg" alt="Ada Lovelace" status /> ```

Initials fallback:

```tsx <Avatar type="initials" size="s" initials="AL" shape="circle" /> ```

## Accesibilidad

- Image type needs meaningful `alt` (person/entity name). Initials/icon types need `aria-label` when the name is not adjacent text. - `status` is decorative presence — do not encode critical state only in the pip color.

### Correcto

- Imagen: alt describe al usuario (ej: 'Karen Ortiz') — no 'avatar' ni 'foto' - Icon default tiene stroke-dasharray para indicar visualmente que es placeholder - Status dot es decorativo — no tiene role ni aria. Comunicar status via texto adyacente - Skeleton oculta todo el contenido con display:none — screen readers no leen nada (correcto)

### Evitar

- No usar Avatar sin alt cuando tiene imagen — es obligatorio para a11y - No depender del status dot como unica indicacion de estado online — acompanar con texto - No usar initials de mas de 2 caracteres — se desborda del espacio

## Cuándo no usar

- Product/hero photography → `Image`. - Stacks of people → `AvatarGroup`.

## Criterio de uso

- Elige `image` cuando la identidad visual importe, `initials` como fallback estable y `icon` para bots o entidades sin rostro. - Usa círculo para personas y cuadrado para marcas, workspaces o entidades no humanas; mantén la decisión consistente en una misma superficie. - Muestra `status` sólo si la presencia es actual y accionable; una pip de color sin información confiable crea una falsa expectativa.

## Gotchas

- La imagen necesita `alt` significativo; initials e icon necesitan un nombre accesible cuando el contexto no lo aporta. - `skeleton` debe desaparecer cuando termina la carga y conservar el tamaño final para evitar saltos de layout.

Uso

import { Avatar } from '@/components/atoms/Avatar';

<Avatar type="image-border" size="m" src="/u/a.jpg" alt="Ada Lovelace" status />

Props

PropTipoDefaultRango / opcionesWhatHow
typeselectimage-border`image`, `image-border`, `initials`, `icon`Content strategy for the avatar face.image-border for product UIs with photo; initials when photo missing; icon for bots. Default: image-border.
shapeselectcircle`circle`, `square`Outer shape of the avatar.circle default for people; square for brands/workspaces. Default: circle.
sizeselects`xs`, `s`, `m`, `l`Pixel scale of the avatar.s in lists; m in headers; l for profile heroes; xs in dense tables. Default: s.
statusbooleanfalse`true` / `false`Shows the presence/status pip.true only when presence is real-time and meaningful. Default: false.
skeletonbooleanfalse`true` / `false`Loading placeholder surface.true while the image URL is resolving. Default: false.

Gotchas

  • a11y

    Provide alt for image type or aria-label for initials/icon so the person/entity is named.

Anatomía CSS

<div class="avatar">
  <span class="avatar__icon"></span>
  <span class="avatar__image"></span>
  <span class="avatar__status"></span>
</div>
ClasePropósito
avatarroot
avatar--circlemodifier
avatar--iconmodifier
avatar--imagemodifier
avatar--image-bordermodifier
avatar--initialsmodifier
avatar--lmodifier
avatar--mmodifier
avatar--smodifier
avatar--skeletonmodifier
avatar--squaremodifier
avatar--xsmodifier
avatar__iconelement
avatar__imageelement
avatar__statuselement

Tokens resueltos

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

VariantePropValorToken
allbg#25d366success
imagebgnone(unparsed)
initialsfg#171717border.focus
initialsborder2pxstroke.medium
iconfg#525252muted.foreground
iconborder2pxstroke.medium
allborder#fafafabackground
skeletonbordernone(unparsed)

Animaciones

PropiedadDuraciónEasing
@keyframes avatar-skeleton-pulse

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).

/* -------------------------------------------------------------------------
   Avatar

   User representation: image, initials, or icon placeholder.
   Optional status dot (online indicator).

   Types:   image, image-border, initials, icon
   Shapes:  circle, square
   Sizes:   xs (24px), s (32px), m (40px), l (48px)
   ------------------------------------------------------------------------- */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  position: relative;
  background-color: #f5f5f5;
}

.avatar__image {
  overflow: hidden;
}

/* ---- Sizes ---- */

.avatar--xs { width: 1.5rem; height: 1.5rem; }
.avatar--s  { width: 2rem; height: 2rem; }
.avatar--m  { width: 2.5rem; height: 2.5rem; }
.avatar--l  { width: 3rem; height: 3rem; }

/* ---- Shapes ---- */

.avatar--circle { border-radius: 9999px; }

.avatar--square.avatar--xs { border-radius: 8px; }
.avatar--square.avatar--s  { border-radius: 8px; }
.avatar--square.avatar--m  { border-radius: 12px; }
.avatar--square.avatar--l  { border-radius: 16px; }

/* ---- Types ---- */

/* Image (no border) */
.avatar--image {
  background: none;
  padding: 0;
}

/* Image with border ring */
.avatar--image-border {
  padding: 0.125rem;
}
.avatar--image-border.avatar--m,
.avatar--image-border.avatar--l {
  padding: 0.25rem;
}

/* Initials */
.avatar--initials {
  border: 2px solid #e5e5e5;
  font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
  font-weight: 400;
  color: #171717;
  font-size: 12.8px;
  line-height: 1;
}
.avatar--initials.avatar--l {
  font-size: 16px;
}

/* Icon */
.avatar--icon {
  border: 2px solid #e5e5e5;
  color: #525252;
}

/* ---- Image element ---- */

.avatar__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: inherit;
}

.avatar--circle .avatar__image {
  border-radius: 9999px;
}

/* ---- Icon element ---- */

.avatar__icon {
  display: flex;
  align-items: center;
  justify-content: center;
}

.avatar--xs .avatar__icon { width: 0.75rem; height: 0.75rem; }
.avatar--s .avatar__icon  { width: 1rem; height: 1rem; }
.avatar--m .avatar__icon  { width: 1.25rem; height: 1.25rem; }
.avatar--l .avatar__icon  { width: 1.5rem; height: 1.5rem; }

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

/* ---- Status dot ---- */

.avatar__status {
  position: absolute;
  background-color: #25d366;
  border-radius: 9999px;
  border-style: solid;
  border-color: #fafafa;
}

.avatar--xs .avatar__status { width: 0.5rem; height: 0.5rem; border-width: 1px; bottom: -0.0625rem; right: -0.0625rem; }
.avatar--s .avatar__status  { width: 0.5rem; height: 0.5rem; border-width: 1px; bottom: 0; right: 0; }
.avatar--m .avatar__status  { width: 0.75rem; height: 0.75rem; border-width: 1.5px; bottom: 0; right: 0; }
.avatar--l .avatar__status  { width: 1rem; height: 1rem; border-width: 2px; bottom: 0; right: 0; }

/* ---- Skeleton ---- */

.avatar--skeleton {
  border: none;
  animation: avatar-skeleton-pulse 1.5s ease-in-out infinite;
}

.avatar--skeleton * {
  display: none;
}

@keyframes avatar-skeleton-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

Codigo fuente

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

type AvatarType = 'image' | 'image-border' | 'initials' | 'icon';
type AvatarShape = 'circle' | 'square';
type AvatarSize = 'xs' | 's' | 'm' | 'l';

export type AvatarProps = {
  type?: AvatarType;
  shape?: AvatarShape;
  size?: AvatarSize;
  src?: string;
  alt?: string;
  initials?: string;
  icon?: ReactNode;
  status?: boolean;
  skeleton?: boolean;
  className?: string;
};

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

const DefaultIcon = () => (
  <svg width="100%" height="100%" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" strokeDasharray="2 2">
    <circle cx="8" cy="6" r="3" />
    <path d="M3 14a5 5 0 0110 0" />
  </svg>
);

export function Avatar({
  type = 'image-border',
  shape = 'circle',
  size = 's',
  src,
  alt = '',
  initials,
  icon,
  status = false,
  skeleton = false,
  className,
}: AvatarProps) {
  const classes = cn(
    'avatar',
    `avatar--${type}`,
    `avatar--${shape}`,
    `avatar--${size}`,
    skeleton && 'avatar--skeleton',
    className,
  );

  const renderContent = () => {
    if (skeleton) return null;

    if (type === 'image' || type === 'image-border') {
      return src ? <img className="avatar__image" src={src} alt={alt} /> : <span className="avatar__icon"><DefaultIcon /></span>;
    }

    if (type === 'initials') {
      return <span>{initials || '??'}</span>;
    }

    if (type === 'icon') {
      return <span className="avatar__icon">{icon || <DefaultIcon />}</span>;
    }

    return null;
  };

  return (
    <span className={classes}>
      {renderContent()}
      {status && !skeleton && <span className="avatar__status" />}
    </span>
  );
}

Webflow

Webflowavatar

Pega en el Designer como application/json, luego convierte a Component (Atom / Avatar) 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 .avatar--square.avatar--xscompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .avatar--square.avatar--scompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .avatar--square.avatar--mcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .avatar--square.avatar--lcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .avatar--image-border.avatar--m, .avatar--image-border.avatar--lcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .avatar--initials.avatar--lcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .avatar--circle .avatar__imagecompound/descendant selector — moved to head Custom Code (Designer styles are single-class)
  • selector on .avatar--xs .avatar__iconcompound/descendant selector — moved to head Custom Code (Designer styles are single-class)

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

Componentes relacionados

On this page

Detalles

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

Componente

Avatar

Source

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