Avatar

Initials Avatar

An avatar whose colour is derived from the name. A list of people with no photos stays readable, and the same person is always the same colour.

  • avatar
  • initials
  • user
  • list

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/avatar-001?lang=en

Anna Kovalyova
Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "avatar-001" (Initials Avatar) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/avatar-001.json

Registry item: https://vibeui.ru/r/avatar-001.json
Installs to: components/vibeui/avatar-001.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
An avatar whose colour is derived from the name. A list of people with no photos stays readable, and the same person is always the same colour.

An avatar with initials and a presence dot. The background hue is derived from the name, so a list of forty photo-less people stays distinguishable and one person keeps their colour across pages and reloads. Three sizes, four presence states, an optional photo. Zero dependencies, one file, its own palette.

## 3. How to use it
import { Avatar001 } from "@/components/vibeui/avatar-001"

<Avatar001 name="Anna Kovalyova" status="online" />

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-avatar-001-* palette — do not swap it for your theme tokens (bg-muted, text-muted-foreground and the like)
- deriving the hue from the name: hueOf is deterministic, while a random colour would change on every render
- tying background and text to a single --vibeui-avatar-001-hue through oklch — that is what keeps contrast at any hue
- the visually hidden full name in [data-part="name"] and aria-hidden on the initials: a screen reader must read the name, not two letters
- the box-shadow ring around the presence dot: without it the dot disappears against a photo
- the sizes driven by the single --vibeui-avatar-001-size variable that the font and dot are calculated from
- the plain <img> instead of next/image: the component has to work in any React project
- the <style> block inside the component — it holds the palette and the geometry

## 6. You may change
- name — the person's name; the initials and the colour both come from it
- src — a photo URL; without it the initials show
- size: sm in lists and tables, md by default, lg on a profile
- status: online, away, busy, or none when presence is not shown
- outer spacing through className

## 7. Rules
- Do not replace hueOf with a random colour or Math.random: a person must always be the same colour.
- Do not derive the colour from an id or email when the UI shows a name: people connect the colour to what they see.
- An avatar stack is a separate component; this one is a single shape.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/avatar-001.json
https://vibeui.ru/r/avatar-001.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-avatar-001-*. Оттенок считается из имени детерминированной функцией hueOf и подставляется в переменную --vibeui-avatar-001-hue: фон и цвет инициалов выводятся из неё через oklch, поэтому контраст держится на любом оттенке. Фото подставляется пропом src обычным <img>: компонент переносим и не зависит от next/image. Полное имя лежит скрытым текстом для скринридера, инициалы помечены aria-hidden.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Avatar001Status = "none" | "online" | "away" | "busy"

export type Avatar001Props = Omit<
  ComponentPropsWithoutRef<"span">,
  "children"
> & {
  name?: string
  /** Ссылка на фото. Без неё показываются инициалы. */
  src?: string
  size?: "sm" | "md" | "lg"
  status?: Avatar001Status
}

// Идея компонента: фон выводится из имени, а не задаётся руками. В списке из
// сорока человек без фотографий аватары всё равно различимы, и один и тот же
// человек всегда одного цвета — на любой странице и после перезагрузки.
const STYLES = `
:where([data-vibeui-block="avatar-001"]){
--vibeui-avatar-001-size:2.5rem;
--vibeui-avatar-001-hue:250;
--vibeui-avatar-001-bg:oklch(0.92 0.05 var(--vibeui-avatar-001-hue));
--vibeui-avatar-001-fg:oklch(0.38 0.09 var(--vibeui-avatar-001-hue));
--vibeui-avatar-001-ring:oklch(1 0 0);
--vibeui-avatar-001-status:oklch(0.63 0.17 152);
--vibeui-avatar-001-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="avatar-001"]{
position:relative;display:inline-flex;flex:none;
width:var(--vibeui-avatar-001-size);height:var(--vibeui-avatar-001-size);
vertical-align:middle;
}
[data-vibeui-block="avatar-001"][data-size="sm"]{--vibeui-avatar-001-size:2rem}
[data-vibeui-block="avatar-001"][data-size="lg"]{--vibeui-avatar-001-size:3.5rem}
[data-vibeui-block="avatar-001"] [data-part="shape"]{
display:flex;align-items:center;justify-content:center;
width:100%;height:100%;overflow:hidden;border-radius:9999px;
background:var(--vibeui-avatar-001-bg);color:var(--vibeui-avatar-001-fg);
font-family:var(--vibeui-avatar-001-font);
font-size:calc(var(--vibeui-avatar-001-size) * 0.36);
font-weight:600;letter-spacing:0.01em;line-height:1;
user-select:none;
}
[data-vibeui-block="avatar-001"] img{width:100%;height:100%;object-fit:cover;display:block}
[data-vibeui-block="avatar-001"] [data-part="status"]{
position:absolute;right:0;bottom:0;
width:calc(var(--vibeui-avatar-001-size) * 0.28);
height:calc(var(--vibeui-avatar-001-size) * 0.28);
border-radius:9999px;
background:var(--vibeui-avatar-001-status);
box-shadow:0 0 0 2px var(--vibeui-avatar-001-ring);
}
[data-vibeui-block="avatar-001"][data-status="away"]{--vibeui-avatar-001-status:oklch(0.75 0.16 75)}
[data-vibeui-block="avatar-001"][data-status="busy"]{--vibeui-avatar-001-status:oklch(0.58 0.2 25)}
/* Имя для скринридера: инициалы он прочитал бы как набор букв. */
[data-vibeui-block="avatar-001"] [data-part="name"]{
position:absolute;width:1px;height:1px;overflow:hidden;
clip-path:inset(50%);white-space:nowrap;
}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="avatar-001"] *{animation:none!important;transition:none!important}}
`

/** Инициалы: первые буквы двух первых слов имени. */
function initialsOf(name: string): string {
  return name
    .trim()
    .split(/\s+/)
    .slice(0, 2)
    .map((word) => word.charAt(0).toUpperCase())
    .join("")
}

/**
 * Оттенок из имени: FNV-1a, разложенный по двенадцати ступеням круга. Одно и
 * то же имя всегда даёт один и тот же цвет. Ступени вместо непрерывного круга
 * нужны потому, что кириллические имена по сумме кодов ложатся в один сектор
 * и весь список получается розовым.
 */
function hueOf(name: string): number {
  let hash = 2166136261

  for (const symbol of name) {
    hash ^= symbol.codePointAt(0)!
    hash = Math.imul(hash, 16777619)
  }

  return ((hash >>> 0) % 12) * 30
}

/**
 * Аватар с инициалами, цвет которого выводится из имени.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Avatar001({
  name = "Анна Ковалёва",
  src,
  size = "md",
  status = "online",
  className,
  style,
  ...props
}: Avatar001Props) {
  const palette = {
    "--vibeui-avatar-001-hue": hueOf(name),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-avatar-001" precedence="medium">
        {STYLES}
      </style>
      <span
        {...props}
        data-vibeui-block="avatar-001"
        data-size={size}
        data-status={status}
        className={className}
        style={palette}
      >
        <span data-part="shape">
          {src ? (
            <img src={src} alt={name} />
          ) : (
            <span aria-hidden="true">{initialsOf(name)}</span>
          )}
        </span>
        {status !== "none" ? (
          <span data-part="status" aria-hidden="true" />
        ) : null}
        <span data-part="name">{name}</span>
      </span>
    </>
  )
}