All components
NucleusAvatar
Data displayNewAn avatar with graceful initials fallback on a stable pastel background, plus an overlapping NucleusAvatarGroup with an overflow chip.
Preview
RM
JP
RMTBJP+2
Installation
- 1shadcn/ui initialized (`npx shadcn@latest init`)
- 2Tailwind CSS v4 configured with the nucleusUI theme tokens
npm install lucide-react clsx tailwind-mergeAdd to your globals.css
The nucleusUI accent lives in these CSS variables. Without them the component falls back to the default shadcn zinc theme.
app/globals.css
:root {
--primary: oklch(0.54 0.24 291);
--primary-foreground: oklch(0.985 0 0);
--ring: oklch(0.54 0.24 291);
--accent: oklch(0.96 0.03 291);
--accent-foreground: oklch(0.4 0.19 291);
}
.dark {
--primary: oklch(0.68 0.21 291);
--primary-foreground: oklch(0.145 0 0);
--ring: oklch(0.68 0.21 291);
--accent: oklch(0.26 0.06 291);
--accent-foreground: oklch(0.87 0.09 291);
}Usage
tsx
import {
NucleusAvatar,
NucleusAvatarGroup,
} from "@/components/nucleus/nucleus-avatar";
export function Example() {
return (
<NucleusAvatarGroup max={3}>
<NucleusAvatar name="Rania Moussa" />
<NucleusAvatar name="Tom Brekke" src="/avatars/tom.jpg" />
<NucleusAvatar name="June Park" size="md" ring />
<NucleusAvatar name="Ada Figueroa" />
</NucleusAvatarGroup>
);
}Source
Copy this file into components/nucleus/nucleus-avatar.tsx in your project, it's yours to own and modify.
components/nucleus/nucleus-avatar.tsx
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
export interface NucleusAvatarProps extends React.ComponentProps<"span"> {
/** Image URL. Falls back to initials when missing or broken. */
src?: string;
/** Alt text for the image (ignored when rendering initials). */
alt?: string;
/** Name used to derive initials (first letters of the first two words). */
name: string;
/** Visual size. */
size?: "sm" | "md" | "lg";
/** Adds a 2px violet ring. */
ring?: boolean;
}
const sizeClasses = {
sm: "size-8 text-xs",
md: "size-10 text-sm",
lg: "size-12 text-base",
} as const;
/** Deterministic fallback hue from the name, so each person keeps their color. */
function hueFromName(name: string) {
let hash = 0;
for (let i = 0; i < name.length; i++) {
hash = (hash * 31 + name.charCodeAt(i)) % 360;
}
return hash;
}
function initialsOf(name: string) {
return name
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((w) => w[0]!.toUpperCase())
.join("");
}
/**
* Avatar with graceful fallback: shows initials on a stable pastel
* background when no image is provided (or the image fails to load).
*/
export function NucleusAvatar({
src,
alt,
name,
size = "md",
ring = false,
className,
...props
}: NucleusAvatarProps) {
const [broken, setBroken] = React.useState(false);
const showImage = Boolean(src) && !broken;
const hue = hueFromName(name);
return (
<span
data-slot="nucleus-avatar"
className={cn(
"relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full font-semibold text-foreground",
sizeClasses[size],
ring && "ring-2 ring-ring/60",
className
)}
style={
showImage
? undefined
: {
backgroundColor: `oklch(0.85 0.09 ${hue})`,
color: `oklch(0.35 0.12 ${hue})`,
}
}
{...props}
>
{showImage ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={src}
alt={alt ?? name}
onError={() => setBroken(true)}
className="size-full object-cover"
/>
) : (
initialsOf(name)
)}
</span>
);
}
/** Horizontally overlapping row of avatars, capped with a "+n" chip. */
export function NucleusAvatarGroup({
max = 4,
className,
children,
...rest
}: React.ComponentProps<"div"> & {
/** Visible avatars before the overflow chip appears. */
max?: number;
className?: string;
}) {
const list = React.Children.toArray(children) as React.ReactElement[];
const visible = list.slice(0, max);
const overflow = list.length - visible.length;
return (
<div
data-slot="nucleus-avatar-group"
className={cn("flex -space-x-2.5", className)}
{...rest}
>
{visible}
{overflow > 0 && (
<span
className={cn(
"inline-flex items-center justify-center rounded-full border border-border bg-muted font-semibold text-foreground",
sizeClasses.md
)}
>
+{overflow}
</span>
)}
</div>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | — | Name used to derive initials and the fallback hue. |
src | string | — | Image URL. Falls back to initials when missing or broken. |
alt | string | — | Alt text for the image (ignored when rendering initials). |
size | "sm" | "md" | "lg" | "md" | Visual size of the avatar. |
ring | boolean | false | Adds a 2px violet ring. |
max | number | 4 | Visible avatars in NucleusAvatarGroup before the overflow chip. |