# Avatar Component
Display people, accounts, or fallback initials with consistent sizing, optional online presence, overlapping groups, and a subtle light/dark outline.
## Features
- Five sizes from extra small through extra large
- Unsplash-compatible image URLs or initials fallback
- Optional accessible online status with a CSS-only pulse
- Static and animated avatar groups
- Size-aware group overlap and remaining-count indicator
- Light and dark mode inset outlines
- Reduced-motion support
- No JavaScript or Stimulus controller required
- Shared Partial and ViewComponent implementations
## Implementation Options
| Format | Location | Best For |
| ------ | -------- | -------- |
| **Plain ERB** | `app/views/components/avatar/` | Copy-paste examples and complete markup control |
| **Shared Partial** | `app/views/shared/components/avatar/` | Reusable rendering from locals and arrays |
| **ViewComponent** | `app/components/avatar/` | Ruby APIs, slots, encapsulation, and testing |
## Shared Partial
### Individual Avatar
```erb
<%= render "shared/components/avatar/avatar",
src: user.avatar_url,
alt: user.name,
size: "md" %>
```
### Online Status
```erb
<%= render "shared/components/avatar/avatar",
src: user.avatar_url,
alt: user.name,
size: "lg",
status: "online",
status_label: "Online" %>
```
### Initials Fallback
When `src` is blank, initials are derived from `alt`. Pass `fallback` to provide explicit text.
```erb
<%= render "shared/components/avatar/avatar",
alt: "Avery Stone",
fallback: "AS",
size: "md" %>
```
### Avatar Group
```erb
<%= render "shared/components/avatar/avatar_group",
avatars: [
{ src: olivia.avatar_url, alt: "Olivia Martin" },
{ src: noah.avatar_url, alt: "Noah Williams", status: "online" },
{ src: emma.avatar_url, alt: "Emma Davis" }
],
size: "md",
remaining_count: 3,
label: "Project members" %>
```
Set `animated: true` to use the compact group that expands to the recommended overlap when the group is hovered.
```erb
<%= render "shared/components/avatar/avatar_group",
avatars: team_members.map { |member| { src: member.avatar_url, alt: member.name } },
size: "md",
remaining_count: 3,
label: "Project members",
animated: true %>
```
## ViewComponent
### Individual Avatar
```erb
<%= render Avatar::Component.new(
src: user.avatar_url,
alt: user.name,
size: :md
) %>
```
### Online Status
```erb
<%= render Avatar::Component.new(
src: user.avatar_url,
alt: user.name,
size: :lg,
status: :online,
status_label: "Online"
) %>
```
### Initials Fallback
```erb
<%= render Avatar::Component.new(
alt: "Avery Stone",
fallback: "AS",
size: :md
) %>
```
### Avatar Group
Use `with_avatar` to add individual avatars. The group applies its `size` to every item.
```erb
<%= render Avatar::GroupComponent.new(
size: :md,
remaining_count: 3,
label: "Project members"
) do |group| %>
<% group.with_avatar(src: olivia.avatar_url, alt: "Olivia Martin") %>
<% group.with_avatar(src: noah.avatar_url, alt: "Noah Williams", status: :online) %>
<% group.with_avatar(src: emma.avatar_url, alt: "Emma Davis") %>
<% end %>
```
### Animated Avatar Group
```erb
<%= render Avatar::GroupComponent.new(
size: :md,
remaining_count: 3,
label: "Project members",
animated: true
) do |group| %>
<% group.with_avatar(src: olivia.avatar_url, alt: "Olivia Martin") %>
<% group.with_avatar(src: noah.avatar_url, alt: "Noah Williams") %>
<% group.with_avatar(src: emma.avatar_url, alt: "Emma Davis") %>
<% end %>
```
The animation uses transforms, lasts 200ms, and is disabled when the user requests reduced motion. Hovering changes only the visual spacing; it does not reveal hidden content or functionality.
## Avatar Options
The Shared Partial uses string values for `size` and `status`; the ViewComponent uses symbols.
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `alt` | String | required | Accessible image description. Use `""` only when the avatar is decorative and nearby text already identifies the person. |
| `src` | String or nil | `nil` | Image URL. When blank, the initials fallback is rendered. |
| `fallback` | String or nil | derived from `alt` | Explicit fallback text. |
| `size` | String/Symbol | `md` | `xs`, `sm`, `md`, `lg`, or `xl`. |
| `status` | String/Symbol or nil | `nil` | Set to `online` to show the green status indicator. |
| `status_label` | String | `"Online"` | Screen-reader text for the status indicator. |
| `pulse` | Boolean | `true` | Enables the CSS ping animation when status is online. |
| `classes` | String or nil | `nil` | Additional classes for the avatar wrapper. |
| `html_options` | Hash | `{}` | Additional wrapper attributes such as `id`, `data`, `aria`, or `title`. |
| `image_options` | Hash | `{}` | Additional attributes for the image element. |
## Avatar Group Options
| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `avatars` | Array of Hashes | required for Shared Partial | Avatar options passed to the individual avatar partial. Every entry requires `alt`. |
| `size` | String/Symbol | `md` | Size applied to every avatar and the count indicator. |
| `remaining_count` | Integer or nil | `nil` | Displays `+N` after visible avatars when greater than zero. |
| `label` | String | `"Avatar group"` | Accessible label for the group. |
| `animated` | Boolean | `false` | Uses compact overlap at rest and expands to normal overlap on hover. |
| `classes` | String or nil | `nil` | Additional classes for the group wrapper. |
| `html_options` | Hash | `{}` | Additional wrapper attributes such as `id`, `data`, `aria`, or `title`. |
ViewComponent group items use the `with_avatar` setter and accept all individual avatar options except `size`, which is controlled by the group.
## Size and Group Overlap Reference
| Variant | Dimensions | Normal Group Overlap | Animated Resting Overlap |
| ------- | ---------- | -------------------- | ------------------------ |
| `xs` | 24px (`size-6`) | `-space-x-2` | `-space-x-4` |
| `sm` | 32px (`size-8`) | `-space-x-3` | `-space-x-5` |
| `md` | 40px (`size-10`) | `-space-x-4` | `-space-x-6` |
| `lg` | 48px (`size-12`) | `-space-x-5` | `-space-x-7` |
| `xl` | 64px (`size-16`) | `-space-x-6` | `-space-x-8` |
Animated groups expand from the resting overlap to the normal overlap for their size.
## Accessibility
- Give portraits a useful `alt` value containing the person's name or another concise description.
- Use `alt: ""` only when adjacent visible text already identifies the same person and the image is redundant.
- Give each group a meaningful `label`, such as `"Project members"` or `"Reviewers"`.
- The remaining-count indicator exposes text such as `"3 more"` to assistive technology.
- Online status includes `status_label`, so it is not communicated by color alone.
- The ping animation is decorative, is hidden from assistive technology, and stops under reduced-motion preferences.
- Do not use the avatar pulse as an `aria-live` region. Update a separate text status if changes must be announced.
## Customization
Use `classes` for layout-level additions and `html_options` or `image_options` for standard attributes:
```erb
<%= render Avatar::Component.new(
src: user.avatar_url,
alt: user.name,
classes: "shadow-sm",
html_options: { data: { user_id: user.id }, title: user.name },
image_options: { loading: "lazy" }
) %>
```
Keep the avatar wrapper `relative` and circular. The `::after` element provides the inset light/dark outline, and isolation keeps each outline inside its avatar when groups overlap.
---
## AI Instructions
### Choose An Implementation
- **Vanilla / plain ERB**: Use when you want full markup control or need to adapt one example directly inside a page.
- **ERB template partial**: Use when you want reusable avatars driven by locals, arrays, or model data.
- **ViewComponent**: Use when you want a Ruby API, slots for grouped avatars, stronger encapsulation, or isolated component tests.
### Quick Reference
- **Vanilla examples**: `app/views/components/avatar/`
- **ERB partial files**: `app/views/shared/components/avatar/`
- **Individual ERB template**: `render "shared/components/avatar/avatar"`
- **Group ERB template**: `render "shared/components/avatar/avatar_group"`
- **Individual ViewComponent**: `render Avatar::Component.new(...)`
- **Group ViewComponent**: `render Avatar::GroupComponent.new(...)`
- **ViewComponent files**: `app/components/avatar/`
- **JavaScript requirement**: none
### Implementation Checklist
- Pick one implementation path first and use its documented value types consistently.
- Always provide `alt` for each avatar, including every group item.
- Use only `xs`, `sm`, `md`, `lg`, or `xl` for size.
- Use `online` as the only status value unless you deliberately add and document another visual treatment.
- Put `size` on the group instead of individual grouped avatars.
- Use `remaining_count` for the visible `+N` item.
- Preserve the `::after` inset outline and isolation classes when adapting the markup.
- Preserve the status text, decorative `aria-hidden` attributes, and reduced-motion classes.
- Keep animation decorative; never make hover the only way to access information.
### Common Patterns
- **Profile image**: Provide `src`, `alt`, and one documented size.
- **Fallback initials**: Omit `src`; optionally provide `fallback`.
- **Presence**: Set `status` to `online` and provide a truthful `status_label`.
- **Static team list**: Use the group with `remaining_count` and a meaningful `label`.
- **Animated team list**: Add `animated: true`; the component handles size-aware compact and normal spacing.
### Common Mistakes
- Do not mix Shared Partial locals with ViewComponent initializer arguments in the same render call.
- Do not omit `alt`, even when a fallback is expected.
- Do not set per-avatar sizes inside a group.
- Do not invent status colors or meanings without adding accessible labels and documentation.
- Do not remove `isolate`; overlapping pseudo-elements can otherwise paint across neighboring avatars.
- Do not replace the documented `after:` shadow with a border or opaque ring.
- Do not add a Stimulus controller; this component is CSS-only.