FairGarden Design

Profile

People and organizations: bylines, author blocks, bio rows and team grids, with Avatar portraits.

import {
  Profile,
  ProfileBio,
  ProfileName,
  ProfilePortrait,
} from '@fairgarden-private/design/components/Profile'

Names set in the serif and roles in caps sans. Use real portraits or initials, never generic silhouettes, and never overlay a name on a photo. ProfileNameLink links the whole profile (or team member) through a title Link, and the item draws the focus ring; social links in ProfileLinks stay separate tab stops. Former members say so in words, never by dimming. Species use the card and testimonials use Quote.

Byline, author block and bio row

Byline, author block and bio row

kind is required and sets the portrait's Avatar size: 32 px in a byline (with "By Name" and the date joined by "·"), 40 px in an author block, 64 px in a bio row, stepping to 128 px from 768 px of container. The bio row keeps the portrait beside the text, even on phones, and stacks below 360 px of container. Avatars keep their own ring and initials face on every ground.

ADBy Ana Díaz
HR

Hemlock Ravine Land Trust

A member-supported land trust caring for 1,200 acres of forest and meadow since 1987.
AD

Ana Díaz

Stewardship director

Ana leads the trail crews and the spring bird count. Before the trust she mapped wetlands for the state and still can’t pass a puddle without checking for eggs.
ProfileKinds.tsx
import { AvatarFallback, AvatarImage } from '@fairgarden-private/design/components/Avatar'
import { Link } from '@fairgarden-private/design/components/Link'
import {
  Profile,
  ProfileBio,
  ProfileDate,
  ProfileDetails,
  ProfileLinks,
  ProfileName,
  ProfileNameLink,
  ProfilePortrait,
  ProfileRole,
} from '@fairgarden-private/design/components/Profile'
import { portrait } from '../portrait'
import styles from './kinds.module.css'

/** The Avatar size follows the kind: byline 32 px, author 40 px, bio 64 px (128 px when wide). */
export function ProfileKinds() {
  return (
    <div className={styles.stack}>
      <Profile kind="byline">
        <ProfilePortrait>
          <AvatarImage src={portrait} alt="" />
          <AvatarFallback>AD</AvatarFallback>
        </ProfilePortrait>
        <ProfileName>
          By <ProfileNameLink href="#kinds">Ana Díaz</ProfileNameLink>
        </ProfileName>
        <ProfileDate dateTime="2026-09-12">12 Sep 2026</ProfileDate>
      </Profile>

      <Profile kind="author">
        <ProfilePortrait>
          <AvatarFallback>HR</AvatarFallback>
        </ProfilePortrait>
        <ProfileName>Hemlock Ravine Land Trust</ProfileName>
        <ProfileBio>
          A member-supported land trust caring for 1,200 acres of forest and meadow since 1987.
        </ProfileBio>
      </Profile>

      <Profile kind="bio">
        <ProfilePortrait>
          <AvatarImage src={portrait} alt="" />
          <AvatarFallback>AD</AvatarFallback>
        </ProfilePortrait>
        <ProfileDetails>
          <ProfileName>Ana Díaz</ProfileName>
          <ProfileRole>Stewardship director</ProfileRole>
          <ProfileBio>
            Ana leads the trail crews and the spring bird count. Before the trust she mapped
            wetlands for the state and still can’t pass a puddle without checking for eggs.
          </ProfileBio>
          <ProfileLinks>
            <li>
              <Link href="#kinds">ana@example.org</Link>
            </li>
            <li>
              <Link href="#kinds">@anadiaz</Link>
            </li>
          </ProfileLinks>
        </ProfileDetails>
      </Profile>
    </div>
  )
}

Team

Team

ProfileTeam lays out ProfileMembers with square ProfileTeamPhotos in a hairline frame: 1-up below 360 px of container, 2-up from 360, 4-up from 768 and up to 5 from 1024. On saturated grounds use only an author block or a single bio row. Team grids print 3-up, photos in grayscale.

ProfileTeamDemo.tsx
import {
  Profile,
  ProfileMember,
  ProfileName,
  ProfileNameLink,
  ProfileRole,
  ProfileTeam,
  ProfileTeamPhoto,
} from '@fairgarden-private/design/components/Profile'
import { portrait } from '../portrait'

const people = [
  { name: 'Ana Díaz', role: 'Stewardship director' },
  { name: 'Ben Okafor', role: 'Trail crew lead' },
  { name: 'Mei Lin', role: 'Education' },
  { name: 'Sam Reyes', role: 'Former board chair' },
  { name: 'Iris Novak', role: 'Development' },
]

/** Team cells: 1-up below 360 px of container, 2-up from 360, 4-up from 768, up to 5 from 1024. */
export function ProfileTeamDemo() {
  return (
    <Profile kind="team">
      <ProfileTeam>
        {people.map((person) => (
          <ProfileMember key={person.name}>
            <ProfileTeamPhoto src={portrait} alt="" />
            <ProfileName>
              <ProfileNameLink href="#team">{person.name}</ProfileNameLink>
            </ProfileName>
            <ProfileRole>{person.role}</ProfileRole>
          </ProfileMember>
        ))}
      </ProfileTeam>
    </Profile>
  )
}

API Reference

Profile

The profile module. A byline holds ProfilePortrait, ProfileName and ProfileDate; an author block ProfilePortrait, ProfileName, ProfileBio; a bio row ProfilePortrait and ProfileDetails (name, role, bio, links); a team ProfileTeam of ProfileMembers, each with a ProfileTeamPhoto, ProfileName and ProfileRole. Use real portraits or initials; never overlay names on photos, never dim former members.

PropTypeDescription
kind
ProfileKind

Required. byline: inline, a 32 px portrait, “By Name” and the date joined by “·". author: the end-of-article block, portrait → serif name → description. bio: portrait at the start (64 px, 128 px from 768 px of container) beside the heading and body, even on phones. team: a grid of square photos with names and roles.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: rings, frames, initials and text. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale: only the name-link and social-link underlines (--role-accent). Never defaulted.

ProfilePortrait and ProfileTeamPhoto

The portrait: an Avatar sized by the kind (byline 32 px, author 40 px, bio 64 px stepping to 128 px from 768 px of container), with its ring per §10.12, taking the module’s primary and secondary unless given its own. Compose AvatarImage (empty alt where the name is adjacent) and AvatarFallback (1–2 initials) inside; the fallback keeps the spacing.

PropTypeDescription
primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: the ring. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale, accepted for the shared contract; no part uses it.

size
'xs' | 'sm' | 'md' | 'lg' | 'xl' | null | undefined

xs 24, sm 32, md 40 (default), lg 64, xl 128 px. Up to 40 px the ring is --border-size-1 --role-rule; from 64 px it is --border-size-2 --primary12. Sizes are fixed at every width.

A team member’s photo: a plain img, square at the cell’s full width, --ds-radius-8, framed in a --border-size-1 --role-hairline line. Give it empty alt when the name follows.

The name in --primary12: type-itemhead for the author block, bio row and team, type-body-ui at --font-weight-6 in a byline (“By Name”). Renders <h3>, or <span> in a byline. Wrap a ProfileNameLink inside to link the profile.

The profile’s one link, inside ProfileName: a §9.3 title Link whose hit area stretches over the member (or the whole profile). Hover, focus and press underline the name; the item draws the focus ring.

PropTypeDescription
external
boolean | null | undefined

Adds the arrow-open mark and “(external site)” for assistive technology. Default false.

index
boolean | null | undefined

Adds the screen-only visited ✓ used in long indexes, such as reference and archive lists. Default false [D174, D175].

list
boolean | null | undefined

With kind="nav": a list link (nav-panel, footer, drawer and breadcrumb lists), whose hover is --role-link-hover color only, plus the --ds-stroke-1-5 --role-accent underline where that ink is --primary12. Without it, nav is bare navigation text (bar and utility items, page numbers, toolbar links), whose hover is the --border-size-2 --role-accent underline (§9.3) [D181]. Default false.

muted
boolean | null | undefined

Rests in --role-muted instead of --primary12, as a breadcrumb’s ancestors. Hover takes --role-link-hover only (the underline is added where that ink is --primary12); with kind="nav" it replaces the bar item’s bare-text underline (§9.3, §9.8) [D181]. Default false.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: the link text and focus ring. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale: the accent underline. Never defaulted; omitted, it inherits the scope.

className
string | undefined

Extra class names, added after the module’s own.

The bio row’s text column beside the portrait: name, role, bio and links.

Role or affiliation: type-label caps in --primary12. Former members say so in words (“Former board chair”); never dim them.

A short bio or description, 3–5 lines of type-body-ui in --primary12; prints in full.

Social and contact links, a ul of li > Link: text links with the --role-accent underline, or inline-tier icons. They sit above a stretched name link as their own tab stops. In print, write handles in text; icon-only links are omitted.

The byline date, after a “·” separator: type-caption in --primary12. Pass dateTime.

ProfileTeam and ProfileMember

The team grid: 1-up below 360 px of container, 2-up from 360, 4-up from 768, up to 5-up from 1024 (auto-fit at 152 px without container-query support). Prints 3-up.

One team member: photo → name → role. A ProfileNameLink links the whole cell.

Additional types

profile
ProfileBioProps

Props for ProfileBio: div props and render.

type ProfileBioProps = useRender.ComponentProps<'div'>
ProfileDateProps

Props for ProfileDate: time props and render.

type ProfileDateProps = useRender.ComponentProps<'time'>
ProfileDetailsProps

Props for ProfileDetails: div props and render.

type ProfileDetailsProps = useRender.ComponentProps<'div'>
ProfileKind

The four profile builds (§12.15).

type ProfileKind = 'byline' | 'author' | 'bio' | 'team'
ProfileLinksProps

Props for ProfileLinks: list props and render.

type ProfileLinksProps = useRender.ComponentProps<'ul'>
ProfileMemberProps

Props for ProfileMember: list-item props and render.

type ProfileMemberProps = useRender.ComponentProps<'li'>
ProfileNameLinkProps

Props for ProfileNameLink: Link props except kind, which is always title.

type ProfileNameLinkProps = {
  /** Extra class names, added after the module's own. */
  className?: string;
  /**
   * Primary Radix scale: the link text and focus ring. Never defaulted;
   * omitted, it inherits the scope [D133].
   */
  primary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  /**
   * Secondary Radix scale: the accent underline. Never defaulted; omitted,
   * it inherits the scope.
   */
  secondary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
  /**
   * With `kind="nav"`: a list link (nav-panel, footer, drawer and breadcrumb
   * lists), whose hover is `--role-link-hover` color only, plus the
   * `--ds-stroke-1-5` `--role-accent` underline where that ink is
   * `--primary12`. Without it, `nav` is bare navigation text (bar and
   * utility items, page numbers, toolbar links), whose hover is the
   * `--border-size-2` `--role-accent` underline (§9.3) [D181]. Default
   * `false`.
   */
  list?: boolean | null;
  /**
   * Adds the screen-only visited ✓ used in long indexes, such as reference
   * and archive lists. Default `false` [D174, D175].
   */
  index?: boolean | null;
  /**
   * Adds the arrow-open mark and "(external site)" for assistive technology.
   * Default `false`.
   */
  external?: boolean | null;
  /**
   * Rests in `--role-muted` instead of `--primary12`, as a breadcrumb's
   * ancestors. Hover takes `--role-link-hover` only (the underline is added
   * where that ink is `--primary12`); with `kind="nav"` it replaces the bar
   * item's bare-text underline (§9.3, §9.8) [D181]. Default `false`.
   */
  muted?: boolean | null;
}
ProfileNameProps

Props for ProfileName: heading props and render (default <h3>; <span> in a byline).

type ProfileNameProps = useRender.ComponentProps<'h3'>
ProfilePortraitProps

Props for ProfilePortrait: Avatar props; size follows the kind unless given.

type ProfilePortraitProps = AvatarProps
ProfileProps

Props for Profile: div props, render, the kind and the color axes.

type ProfileProps = React.ClassAttributes<HTMLDivElement> &
  React.HTMLAttributes<HTMLDivElement> &
  __type & {
    kind: ProfileKind;
    primary?:
      | 'ruby'
      | 'olive'
      | 'sage'
      | 'slate'
      | 'sand'
      | 'gray'
      | 'mauve'
      | 'brown'
      | 'bronze'
      | 'gold'
      | 'red'
      | 'crimson'
      | 'tomato'
      | 'pink'
      | 'plum'
      | 'indigo'
      | 'iris'
      | 'violet'
      | 'purple'
      | null;
    secondary?:
      | 'ruby'
      | 'olive'
      | 'sage'
      | 'slate'
      | 'sand'
      | 'gray'
      | 'mauve'
      | 'brown'
      | 'bronze'
      | 'gold'
      | 'red'
      | 'crimson'
      | 'tomato'
      | 'pink'
      | 'plum'
      | 'indigo'
      | 'iris'
      | 'violet'
      | 'purple'
      | 'amber'
      | 'blue'
      | 'cyan'
      | 'grass'
      | 'green'
      | 'jade'
      | 'lime'
      | 'mint'
      | 'orange'
      | 'sky'
      | 'teal'
      | 'yellow'
      | null;
  }
ProfileRoleProps

Props for ProfileRole: paragraph props and render.

type ProfileRoleProps = useRender.ComponentProps<'p'>
ProfileTeamPhotoProps

Props for ProfileTeamPhoto: img props and render.

type ProfileTeamPhotoProps = useRender.ComponentProps<'img'>
ProfileTeamProps

Props for ProfileTeam: list props and render.

type ProfileTeamProps = useRender.ComponentProps<'ul'>

Specification: DESIGN-SYSTEM.md §12.15 (profile and team) and §10.12 (avatar).