Team
Team Profile
One person: a tall portrait, name, address, bio and a list of ways to reach them.
Layout and motion from the Findit template — see it in contextimport { cn } from "@/lib/utils";
/*
* Team Profile — one person: a tall portrait, their name and address, a bio, and how to reach them.
* Source: marcframe/findit → components/sections/AgentMainSection.tsx (+ design-system ContactList).
*
* Layout kept from Framer: below 768px everything stacks; from 768px the portrait (320 × 457) sits beside a column of
* the details and the contact list; from 1024px details and contact share one row aligned to the bottom, 96px apart,
* the contact list up to 325px. Contact rows: a label at the left and a link at the right between thin lines — phone
* numbers dial, WhatsApp opens in a new tab, email writes. No motion besides the links' underline.
*/
export type ProfileContact = { label: string; value: string; href: string };
export type TeamProfileProps = {
/** Heading level of the name: h1 when the block opens the page. */
as?: "h1" | "h2";
name?: string;
address?: string;
bio?: string;
portrait?: { src: string; alt: string };
contactTitle?: string;
contacts?: ProfileContact[];
className?: string;
};
const defaults = {
name: "Steve Parker",
address: "560 Third Avenue, New York, NY 10016",
bio: "Steve has helped more than three hundred families buy and sell homes across Manhattan and Brooklyn. He knows the buildings, the boards and the streets, and he answers his phone on weekends. Before real estate he spent ten years in architecture, which is why his clients ask him to look at the floor plan before the view.",
portrait: {
src: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=640&h=914&q=80",
alt: "Steve Parker",
},
contactTitle: "Contact me",
contacts: [
{ label: "Office phone", value: "+1 (212) 555 0142", href: "tel:+12125550142" },
{ label: "Mobile phone", value: "+1 (917) 555 0187", href: "tel:+19175550187" },
{ label: "WhatsApp", value: "+1 917 555 0187", href: "https://wa.me/19175550187" },
{ label: "Email", value: "steve@harborhomes.example", href: "mailto:steve@harborhomes.example" },
],
};
export function TeamProfile({
as: Heading = "h2",
name = defaults.name,
address = defaults.address,
bio = defaults.bio,
portrait = defaults.portrait,
contactTitle = defaults.contactTitle,
contacts = defaults.contacts,
className,
}: TeamProfileProps) {
return (
<section className={cn("py-(--section-y)", className)}>
<div className="mx-auto flex max-w-(--container) flex-col items-start gap-12 px-(--gutter) md:flex-row md:gap-8 lg:items-end lg:gap-12">
<img
src={portrait.src}
alt={portrait.alt}
className="aspect-[320/457] w-full max-w-80 shrink-0 rounded-xl bg-muted object-cover md:w-80"
/>
<div className="flex w-full flex-col items-start gap-12 md:flex-1 lg:flex-row lg:items-end lg:gap-24">
<div className="flex w-full flex-col gap-12 lg:flex-1">
<div className="flex flex-col gap-2.5">
<Heading className="font-heading text-h2 font-semibold tracking-tight text-balance">{name}</Heading>
{address && <p className="text-muted-foreground">{address}</p>}
</div>
{bio && <p className="text-pretty">{bio}</p>}
</div>
{contacts.length > 0 && (
<div className="flex w-full max-w-81 flex-col gap-5 lg:flex-1">
<h3 className="font-heading text-lg font-semibold tracking-tight">{contactTitle}</h3>
<dl className="border-t border-border text-sm">
{contacts.map((contact) => {
const external = /^https?:\/\//.test(contact.href);
return (
<div key={contact.label} className="flex justify-between gap-4 border-b border-border py-1.5">
<dt>{contact.label}</dt>
<dd className="text-muted-foreground">
<a
href={contact.href}
{...(external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
className="transition-colors hover:text-foreground hover:underline hover:underline-offset-4"
>
{contact.value}
</a>
</dd>
</div>
);
})}
</dl>
</div>
)}
</div>
</div>
</section>
);
}Installation
$ npx shadcn@latest add https://framebrick.co/r/blocks/team-profile.json1. Add the block tokens to
app/globals.cssonce (theming).npx shadcn@latest add https://framebrick.co/r/blocks/tokens.json2. Copy the block into
components/team-profile.tsx.import { cn } from "@/lib/utils"; /* * Team Profile — one person: a tall portrait, their name and address, a bio, and how to reach them. * Source: marcframe/findit → components/sections/AgentMainSection.tsx (+ design-system ContactList). * * Layout kept from Framer: below 768px everything stacks; from 768px the portrait (320 × 457) sits beside a column of * the details and the contact list; from 1024px details and contact share one row aligned to the bottom, 96px apart, * the contact list up to 325px. Contact rows: a label at the left and a link at the right between thin lines — phone * numbers dial, WhatsApp opens in a new tab, email writes. No motion besides the links' underline. */ export type ProfileContact = { label: string; value: string; href: string }; export type TeamProfileProps = { /** Heading level of the name: h1 when the block opens the page. */ as?: "h1" | "h2"; name?: string; address?: string; bio?: string; portrait?: { src: string; alt: string }; contactTitle?: string; contacts?: ProfileContact[]; className?: string; }; const defaults = { name: "Steve Parker", address: "560 Third Avenue, New York, NY 10016", bio: "Steve has helped more than three hundred families buy and sell homes across Manhattan and Brooklyn. He knows the buildings, the boards and the streets, and he answers his phone on weekends. Before real estate he spent ten years in architecture, which is why his clients ask him to look at the floor plan before the view.", portrait: { src: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&crop=faces&w=640&h=914&q=80", alt: "Steve Parker", }, contactTitle: "Contact me", contacts: [ { label: "Office phone", value: "+1 (212) 555 0142", href: "tel:+12125550142" }, { label: "Mobile phone", value: "+1 (917) 555 0187", href: "tel:+19175550187" }, { label: "WhatsApp", value: "+1 917 555 0187", href: "https://wa.me/19175550187" }, { label: "Email", value: "steve@harborhomes.example", href: "mailto:steve@harborhomes.example" }, ], }; export function TeamProfile({ as: Heading = "h2", name = defaults.name, address = defaults.address, bio = defaults.bio, portrait = defaults.portrait, contactTitle = defaults.contactTitle, contacts = defaults.contacts, className, }: TeamProfileProps) { return ( <section className={cn("py-(--section-y)", className)}> <div className="mx-auto flex max-w-(--container) flex-col items-start gap-12 px-(--gutter) md:flex-row md:gap-8 lg:items-end lg:gap-12"> <img src={portrait.src} alt={portrait.alt} className="aspect-[320/457] w-full max-w-80 shrink-0 rounded-xl bg-muted object-cover md:w-80" /> <div className="flex w-full flex-col items-start gap-12 md:flex-1 lg:flex-row lg:items-end lg:gap-24"> <div className="flex w-full flex-col gap-12 lg:flex-1"> <div className="flex flex-col gap-2.5"> <Heading className="font-heading text-h2 font-semibold tracking-tight text-balance">{name}</Heading> {address && <p className="text-muted-foreground">{address}</p>} </div> {bio && <p className="text-pretty">{bio}</p>} </div> {contacts.length > 0 && ( <div className="flex w-full max-w-81 flex-col gap-5 lg:flex-1"> <h3 className="font-heading text-lg font-semibold tracking-tight">{contactTitle}</h3> <dl className="border-t border-border text-sm"> {contacts.map((contact) => { const external = /^https?:\/\//.test(contact.href); return ( <div key={contact.label} className="flex justify-between gap-4 border-b border-border py-1.5"> <dt>{contact.label}</dt> <dd className="text-muted-foreground"> <a href={contact.href} {...(external ? { target: "_blank", rel: "noopener noreferrer" } : {})} className="transition-colors hover:text-foreground hover:underline hover:underline-offset-4" > {contact.value} </a> </dd> </div> ); })} </dl> </div> )} </div> </div> </section> ); }
Usage
import { TeamProfile } from "@/components/team-profile";
<TeamProfile
as="h1"
name="Steve Parker"
portrait={{ src: "/team/steve.jpg", alt: "Steve Parker" }}
contacts={[
{ label: "Mobile phone", value: "+1 917 555 0187", href: "tel:+19175550187" },
{ label: "Email", value: "steve@example.com", href: "mailto:steve@example.com" },
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| as | "h1" | "h2" | "h2" | Heading level of the name — h1 when the block opens the page. |
| name / address / bio | string | — | The person, a muted line under the name, and a paragraph. |
| portrait | { src, alt } | — | Tall photo, 320 × 457; alt is usually the name. |
| contactTitle | string | Contact me | Heading of the contact list (h3). |
| contacts | { label, value, href }[] | — | Rows of a description list; http(s) links (WhatsApp) open in a new tab. |
| className | string | — | Classes for the <section>. |
- Stacked below 768px; from 768px the portrait sits beside a column of details and contacts; from 1024px details and contacts share a row, aligned to the bottom.
- No motion besides the links' underline.
AI prompt
Paste into v0, Lovable, Bolt or Claude to rebuild this block in your own project.
Create a React + Tailwind CSS block called TeamProfile. - <section className="py-(--section-y)">, container mx-auto max-w-(--container) px-(--gutter): stacked (gap-12); from 768px a row (gap-8), from 1024px items-end (gap-12). - A 320 × 457 portrait (rounded-xl, object-cover). Beside it a column that becomes a row from 1024px (items-end, gap-24): the name (h2 or h1, font-heading text-h2 semibold) over a muted address, then the bio; and, up to 325px, an h3 "Contact me" (font-heading text-lg) over a <dl> with a border-t and rows (flex justify-between, border-b, py-1.5, text-sm): the label, and the value as a muted link (tel:, mailto:, or https://wa.me/… in a new tab) that underlines on hover. - Only shadcn variables and block tokens; plain <img> with alt text.











