AvatarGroup
Faces stack with a +N overflow when the list grows. Team members and multi-assignee fields.
Sizes: xssml (default: s)
Editorial
<!-- F12c editorial — non-derivable only. Review: Karen. -->
## Ejemplos
Team row with overflow:
```tsx import { AvatarGroup } from '@/components/atoms/AvatarGroup'; import { Avatar } from '@/components/atoms/Avatar';
<AvatarGroup size="s" max={3}> <Avatar type="image" src="/u/1.jpg" alt="A" /> <Avatar type="image" src="/u/2.jpg" alt="B" /> <Avatar type="image" src="/u/3.jpg" alt="C" /> <Avatar type="image" src="/u/4.jpg" alt="D" /> </AvatarGroup> ```
## Accesibilidad
- Each child `Avatar` still needs its own name (`alt` / `aria-label`). - Overflow “+N” is visual — provide a textual count nearby when the full roster matters (e.g. “4 assignees”).
### Correcto
- Cada Avatar hijo debe tener alt con el nombre del usuario - Count badge (+N) es decorativo — complementar con texto como 'y 3 mas' fuera del grupo - Z-index descendente: primer avatar = mas importante visualmente (encima de los demas)
### Evitar
- No usar max sin indicar cuantos usuarios hay en total — '+3' sin contexto no es util - No mezclar sizes distintos entre AvatarGroup y sus Avatar hijos - No usar mas de 10 avatares visibles — el z-index stack solo cubre 10 niveles
## Cuándo no usar
- Single person → `Avatar`. - Editable assignee pickers that need full names listed → list/`Item` rows, not only a face stack.
## Criterio de uso
- Usa el grupo para resumir participantes cuando el espacio es limitado y el detalle completo puede abrirse en otro contexto. - Elige `max` según el ancho real de la superficie; un `+N` debe representar a todas las personas ocultas, no sólo a las que no cupieron en la primera fila. - Mantén tamaño y forma consistentes con los Avatars vecinos y con la densidad del producto.
## Gotchas
- El overflow visual necesita resumen textual accesible, especialmente si la lista representa responsables o permisos. - Sólo acepta Avatars como hijos; no mezcles texto o controles sin un patrón de lista explícito. - **Nota**: CSS requiere Avatar standalone CSS como prerequisito. AvatarGroup solo agrega overlap, z-index stack y count badge.
Uso
import { Avatar, AvatarGroup } from '@/components/atoms/Avatar';
<AvatarGroup size="s" max={3}>
<Avatar type="initials" initials="AL" alt="Ada" />
<Avatar type="initials" initials="GR" alt="Grace" />
<Avatar type="initials" initials="KT" alt="Katherine" />
<Avatar type="initials" initials="XX" alt="Extra" />
</AvatarGroup>Props
| Prop | Tipo | Default | Rango / opciones | What | How |
|---|---|---|---|---|---|
| size | select | s | `xs`, `s`, `m`, `l` | Size applied to all children avatars. | s in lists; m in page headers. Keep consistent with surrounding density. Default: s. |
| shape | select | circle | `circle`, `square` | Shape applied to children. | Match Avatar shape in the same product surface. Default: circle. |
| max | number | — | 1–12 step 1 count | Max faces before +N overflow. | 3–5 in cards; higher only when space is guaranteed. |
Gotchas
- react
Children must be Avatar components; max trims visually and should be reflected in accessible summary text (+N people).
Anatomía CSS
<div class="avatar-group"> <span class="avatar-group__count"></span> </div>
| Clase | Propósito |
|---|---|
avatar-group | root |
avatar-group--l | modifier |
avatar-group--m | modifier |
avatar-group--s | modifier |
avatar-group--square | modifier |
avatar-group--xs | modifier |
avatar-group__count | element |
Tokens resueltos
Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.
| Variante | Prop | Valor | Token |
|---|---|---|---|
| all | bg | #f5f5f5 | muted |
| all | fg | #525252 | muted.foreground |
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 Group
Stacked row of avatars with negative overlap.
Wraps standard Avatar components.
Sizes: xs, s, m, l (match Avatar sizes)
Parts: .avatar-group, .avatar-group__count
------------------------------------------------------------------------- */
.avatar-group {
display: inline-flex;
align-items: center;
}
/* Negative overlap per size */
.avatar-group--xs > .avatar { margin-left: -0.375rem; }
.avatar-group--s > .avatar { margin-left: -0.5rem; }
.avatar-group--m > .avatar { margin-left: -0.625rem; }
.avatar-group--l > .avatar { margin-left: -0.75rem; }
.avatar-group > .avatar:first-child { margin-left: 0; }
/* Z-index: first child on top, descending */
.avatar-group > .avatar,
.avatar-group > .avatar-group__count {
position: relative;
}
.avatar-group > :nth-child(1) { z-index: 10; }
.avatar-group > :nth-child(2) { z-index: 9; }
.avatar-group > :nth-child(3) { z-index: 8; }
.avatar-group > :nth-child(4) { z-index: 7; }
.avatar-group > :nth-child(5) { z-index: 6; }
.avatar-group > :nth-child(6) { z-index: 5; }
.avatar-group > :nth-child(7) { z-index: 4; }
.avatar-group > :nth-child(8) { z-index: 3; }
.avatar-group > :nth-child(9) { z-index: 2; }
.avatar-group > :nth-child(10) { z-index: 1; }
/* Ring to separate overlapping avatars */
.avatar-group > .avatar {
box-shadow: 0 0 0 2px #fafafa;
}
/* Count badge (+3 more) */
.avatar-group__count {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 9999px;
background-color: #f5f5f5;
color: #525252;
font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
font-weight: 500;
line-height: 1;
box-shadow: 0 0 0 2px #fafafa;
}
/* Count badge inherits square shape */
.avatar-group--square .avatar-group__count { border-radius: 8px; }
.avatar-group--square.avatar-group--m .avatar-group__count { border-radius: 12px; }
.avatar-group--square.avatar-group--l .avatar-group__count { border-radius: 16px; }
.avatar-group--xs .avatar-group__count { width: 1.5rem; height: 1.5rem; font-size: 10.24px; margin-left: -0.375rem; }
.avatar-group--s .avatar-group__count { width: 2rem; height: 2rem; font-size: 10.24px; margin-left: -0.5rem; }
.avatar-group--m .avatar-group__count { width: 2.5rem; height: 2.5rem; font-size: 12.8px; margin-left: -0.625rem; }
.avatar-group--l .avatar-group__count { width: 3rem; height: 3rem; font-size: 12.8px; margin-left: -0.75rem; }
Codigo fuente
import { type ReactNode } from 'react'; function cn(...classes: (string | false | undefined | null)[]) { return classes.filter(Boolean).join(' '); } type AvatarGroupSize = 'xs' | 's' | 'm' | 'l'; type AvatarGroupShape = 'circle' | 'square'; export type AvatarGroupProps = { size?: AvatarGroupSize; shape?: AvatarGroupShape; max?: number; children: ReactNode; className?: string; }; export function AvatarGroup({ size = 's', shape = 'circle', max, children, className }: AvatarGroupProps) { const items = Array.isArray(children) ? children : [children]; const visible = max ? items.slice(0, max) : items; const remaining = max ? items.length - max : 0; return ( <div className={cn('avatar-group', `avatar-group--${size}`, shape === 'square' && 'avatar-group--square', className)}> {visible} {remaining > 0 && ( <span className="avatar-group__count">+{remaining}</span> )} </div> ); }
Webflow
Pega en el Designer como application/json, luego convierte a Component (Atom / AvatarGroup) 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)
selectoron.avatar-group--xs > .avatar— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.avatar-group--s > .avatar— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.avatar-group--m > .avatar— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.avatar-group--l > .avatar— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.avatar-group > .avatar:first-child— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.avatar-group > .avatar, .avatar-group > .avatar-group__count— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.avatar-group > :nth-child(1)— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)selectoron.avatar-group > :nth-child(2)— compound/descendant selector — moved to head Custom Code (Designer styles are single-class)
Tras pegar: Create component → nombre Atom / AvatarGroup → Publish.