Alex Rivera
@alexrivera
cards
A social-style profile card: a full-width token-gradient cover banner (linear-gradient --primary → --accent, replacing the original hardcoded #FC726E SVG), a circular initials-avatar disc that overlaps the banner/body seam, a name + handle below, and a Follow (primary) + Message (ghost) action row. Both buttons are keyboard-operable with focus-visible rings. Fully server-renderable — no hooks or JS state required. Light/dark-robust by construction: every color comes from tokens.
Ported from andrew-demchenk0 (MIT)
@alexrivera
1. Register the namespace (once per project):
// components.json — register the @bottega namespace once
{
"registries": {
"@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
}
}2. Add the component:
npx shadcn add @bottega/profile-user-card<ProfileUserCard />| Prop | Type | Default | Description |
|---|---|---|---|
| name | string | Alex Rivera | Display name shown below the avatar. |
| handle | string | @alexrivera | Handle or role shown below the name (e.g. "@alexrivera" or "Web Developer"). |
| initials | string | AR | Up to 2-character initials rendered on the avatar disc. |
| primaryLabel | string | Follow | Label for the primary action button (default "Follow"). |
| ghostLabel | string | Message | Label for the ghost action button (default "Message"). |
| className | string | Additional class names merged onto the root element. |
import styles from "./profile-user-card.module.css";
export interface ProfileUserCardProps {
/** Display name shown below the avatar. */
name?: string;
/** Handle or role shown below the name (e.g. "@alexrivera" or "Web Developer"). */
handle?: string;
/** Up to 2-character initials rendered on the avatar disc. */
initials?: string;
/** Label for the primary action button (default "Follow"). */
primaryLabel?: string;
/** Label for the ghost action button (default "Message"). */
ghostLabel?: string;
/** Additional class names merged onto the root element. */
className?: string;
}
/**
* ProfileUserCard — social-style profile card with a token-gradient cover
* banner, a circular initials avatar overlapping the banner/body seam,
* a name + handle, and Follow/Message action buttons.
*
* Port of: https://uiverse.io/andrew-demchenk0/chatty-hound-50 (MIT, andrew-demchenk0)
* Token map: --main-color → --foreground; --bg-color → --background;
* --submain-color → color-mix(oklch, --foreground 60%, --background);
* hardcoded #FC726E SVG gradient → linear-gradient(--primary → --accent)
*/
export function ProfileUserCard({
name = "Alex Rivera",
handle = "@alexrivera",
initials = "AR",
primaryLabel = "Follow",
ghostLabel = "Message",
className = "",
}: ProfileUserCardProps) {
const safeInitials = initials.slice(0, 2).toUpperCase();
return (
<article
className={[styles.card, className].filter(Boolean).join(" ")}
aria-label={`Profile card for ${name}`}
>
{/* Cover banner — token gradient replaces hardcoded #FC726E SVG */}
<div className={styles.banner} aria-hidden="true" />
{/* Avatar ring + initials disc; aria-hidden — name below carries the meaning */}
<div className={styles.avatarRing} aria-hidden="true">
<div className={styles.initialsDisc}>{safeInitials}</div>
</div>
{/* Name */}
<h3 className={styles.name}>{name}</h3>
{/* Handle / subtitle */}
<p className={styles.handle}>{handle}</p>
{/* Action buttons — both keyboard-operable with focus-visible rings */}
<div className={styles.actions}>
<button type="button" className={styles.btnGhost}>
{ghostLabel}
</button>
<button type="button" className={styles.btnPrimary}>
{primaryLabel}
</button>
</div>
</article>
);
}