Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions avatar-group/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# AvatarGroup
A flexible avatar component that displays user profile images in circular containers with configurable sizes, automatic initials fallback, group mode with overlap, and status indicators.

## Getting Started

Install dependencies:
```bash
npm install
```

Share the component to your Webflow workspace:
```bash
npx webflow library share
```

For local development:
```bash
npm run dev
```

## Designer Properties

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| ID | Id | — | HTML ID attribute for targeting |
| Mode | Variant | single | Display mode for single or grouped avatars (single, group) |
| Size | Variant | medium | Avatar size variant (small, medium, large, extra-large) |
| Show Border | Boolean | false | Show border ring around avatars |
| Overlap Amount | Variant | medium | Amount of overlap between avatars in group mode (none, small, medium, large) |
| Max Count | Number | 4 | Maximum number of avatars to show before displaying overflow indicator |
| Avatar 1 Image | Image | — | First avatar image URL |
| Avatar 1 Name | Text | John Doe | First avatar name for initials fallback |
| Avatar 1 Status | Variant | none | First avatar status indicator (none, online, offline, busy) |
| Avatar 1 Visible | Visibility | — | Show or hide the first avatar |
| Avatar 2 Image | Image | — | Second avatar image URL |
| Avatar 2 Name | Text | Jane Smith | Second avatar name for initials fallback |
| Avatar 2 Status | Variant | none | Second avatar status indicator (none, online, offline, busy) |
| Avatar 2 Visible | Visibility | — | Show or hide the second avatar |
| Avatar 3 Image | Image | — | Third avatar image URL |
| Avatar 3 Name | Text | Mike Johnson | Third avatar name for initials fallback |
| Avatar 3 Status | Variant | none | Third avatar status indicator (none, online, offline, busy) |
| Avatar 3 Visible | Visibility | — | Show or hide the third avatar |
| Avatar 4 Image | Image | — | Fourth avatar image URL |
| Avatar 4 Name | Text | Sarah Williams | Fourth avatar name for initials fallback |
| Avatar 4 Status | Variant | none | Fourth avatar status indicator (none, online, offline, busy) |
| Avatar 4 Visible | Visibility | — | Show or hide the fourth avatar |
| Avatar 5 Image | Image | — | Fifth avatar image URL |
| Avatar 5 Name | Text | David Brown | Fifth avatar name for initials fallback |
| Avatar 5 Status | Variant | none | Fifth avatar status indicator (none, online, offline, busy) |
| Avatar 5 Visible | Visibility | — | Show or hide the fifth avatar |
| Total Count | Number | 5 | Total number of avatars for overflow calculation (when exceeds maxCount) |

## Styling

This component automatically adapts to your Webflow site's design system through site variables and inherited properties.

### Site Variables

To match your site's design system, define these CSS variables in your Webflow project settings. The component will use the fallback values shown below until you configure them.

| Site Variable | What It Controls | Fallback |
|---------------|------------------|----------|
| --background-primary | Avatar background, overflow indicator background, and border ring background | #ffffff |
| --text-primary | Initials text color and overflow indicator text color | #1a1a1a |
| --border-color | Avatar border ring color and overflow indicator border | #e5e5e5 |

### Inherited Properties

The component inherits these CSS properties from its parent element:
- `font-family` — Typography style for initials and overflow text
- `color` — Base text color
- `line-height` — Text spacing

## Extending in Code

### Custom Avatar Background Colors

Generate unique background colors for initials based on user names:

```javascript
// Add to your component logic
function getAvatarColor(name) {
const colors = ['#3b82f6', '#8b5cf6', '#ec4899', '#f59e0b', '#10b981'];
const hash = name.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
return colors[hash % colors.length];
}

// Apply to avatar wrapper
avatarElement.style.setProperty('--wf-avatargroup-bg-color', getAvatarColor(name));
```

### Dynamic Avatar Loading

Populate avatars from an API or CMS data source:

```javascript
// Fetch user data and update avatar properties
async function loadTeamMembers() {
const users = await fetch('/api/team').then(r => r.json());

users.slice(0, 5).forEach((user, index) => {
const avatarNum = index + 1;
component.props[`avatar${avatarNum}Image`] = user.avatarUrl;
component.props[`avatar${avatarNum}Name`] = user.name;
component.props[`avatar${avatarNum}Status`] = user.onlineStatus;
});

component.props.totalCount = users.length;
}
```

## Dependencies

No external dependencies.
17 changes: 17 additions & 0 deletions avatar-group/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AvatarGroup</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; }
body { color: inherit; }
h1, h2, h3, h4, h5, h6 { color: inherit; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
5 changes: 5 additions & 0 deletions avatar-group/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Avatar Group",
"description": "Stacked avatar circles with overflow count indicator",
"category": "Data Display"
}
25 changes: 25 additions & 0 deletions avatar-group/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "avatar-group",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
"devDependencies": {
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.3",
"@webflow/data-types": "^1.0.1",
"@webflow/react": "^1.0.1",
"@webflow/webflow-cli": "^1.8.44",
"typescript": "~5.8.3",
"vite": "^7.1.7"
}
}
1 change: 1 addition & 0 deletions avatar-group/screenshot-brand.b64

Large diffs are not rendered by default.

Binary file added avatar-group/screenshot-brand.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions avatar-group/screenshot-dark.b64

Large diffs are not rendered by default.

Binary file added avatar-group/screenshot-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions avatar-group/screenshot-light.b64

Large diffs are not rendered by default.

Binary file added avatar-group/screenshot-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
159 changes: 159 additions & 0 deletions avatar-group/src/components/AvatarGroup/AvatarGroup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Webflow Site Variables Used:
* - --background-primary: Avatar background and overflow indicator
* - --text-primary: Initials and overflow text
* - --border-color: Avatar border ring
* - --accent-color: Status indicator colors (fallback)
*/

/* Box sizing reset */
.wf-avatargroup *,
.wf-avatargroup *::before,
.wf-avatargroup *::after {
box-sizing: border-box;
}

/* Root element - inherit Webflow typography + default padding */
.wf-avatargroup {
font-family: inherit;
color: inherit;
line-height: inherit;
padding: 24px;
--wf-avatargroup-size: 40px;
--wf-avatargroup-overlap: -12px;
--wf-avatargroup-bg-color: transparent;
}

/* Container */
.wf-avatargroup-container {
display: flex;
align-items: center;
width: fit-content;
}

/* Single mode - no overlap */
.wf-avatargroup--single .wf-avatargroup-container {
gap: 0;
}

/* Group mode - apply overlap */
.wf-avatargroup--group .wf-avatargroup-avatar {
margin-left: var(--wf-avatargroup-overlap);
}

.wf-avatargroup--group .wf-avatargroup-avatar:first-child {
margin-left: 0;
}

.wf-avatargroup--group .wf-avatargroup-overflow {
margin-left: var(--wf-avatargroup-overlap);
}

/* Avatar wrapper */
.wf-avatargroup-avatar {
position: relative;
width: var(--wf-avatargroup-size);
height: var(--wf-avatargroup-size);
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: var(--wf-avatargroup-bg-color);
flex-shrink: 0;
}

/* Avatar with border */
.wf-avatargroup-avatar--bordered {
box-shadow: 0 0 0 2px var(--background-primary, #ffffff);
border: 1px solid var(--border-color, #e5e5e5);
}

/* Avatar image */
.wf-avatargroup-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}

/* Avatar initials */
.wf-avatargroup-initials {
color: #ffffff;
font-weight: 600;
font-size: calc(var(--wf-avatargroup-size) * 0.4);
line-height: 1;
user-select: none;
}

/* Size variants - adjust initials font size */
.wf-avatargroup--small .wf-avatargroup-initials {
font-size: 12px;
}

.wf-avatargroup--medium .wf-avatargroup-initials {
font-size: 14px;
}

.wf-avatargroup--large .wf-avatargroup-initials {
font-size: 16px;
}

.wf-avatargroup--extra-large .wf-avatargroup-initials {
font-size: 20px;
}

/* Status indicator */
.wf-avatargroup-status {
position: absolute;
bottom: 0;
right: 0;
width: calc(var(--wf-avatargroup-size) * 0.25);
height: calc(var(--wf-avatargroup-size) * 0.25);
border-radius: 50%;
border: 2px solid var(--background-primary, #ffffff);
}

/* Status variants */
.wf-avatargroup-status--online {
background: #10b981;
}

.wf-avatargroup-status--offline {
background: #6b7280;
}

.wf-avatargroup-status--busy {
background: #ef4444;
}

/* Overflow indicator */
.wf-avatargroup-overflow {
background: var(--background-primary, #ffffff);
border: 1px solid var(--border-color, #e5e5e5);
}

.wf-avatargroup-overflow-text {
color: var(--text-primary, #1a1a1a);
font-weight: 600;
font-size: calc(var(--wf-avatargroup-size) * 0.35);
line-height: 1;
user-select: none;
}

/* Size variants - adjust overflow text font size */
.wf-avatargroup--small .wf-avatargroup-overflow-text {
font-size: 11px;
}

.wf-avatargroup--medium .wf-avatargroup-overflow-text {
font-size: 13px;
}

.wf-avatargroup--large .wf-avatargroup-overflow-text {
font-size: 15px;
}

.wf-avatargroup--extra-large .wf-avatargroup-overflow-text {
font-size: 18px;
}
Loading