Nasaq
Components

Profile page

A two-column profile page: an identity column (avatar, name, handle, links, one action, bio, widgets) beside about, experience, skills, projects, writing and testimonials. The owner also sees their apps and account details.

PreviewOpen ↗

Code

<ProfilePage profile={profile} blogHref="/blog" postHref={(p) => `/blog/${p.slug}`} onContact={openForm} />

Website · beta

Live examples and controls: Profile page in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/profile-page.json

A page about a person, in two columns. The identity column holds the avatar, name, @handle, headline, location, availability and links, one full-width action, the bio and the join date, with personal widgets below it (local clock, weather, numbers, now). The content column holds your own sections first, then about, experience (with an overlap-aware total), grouped skills, projects (a featured story, then a grid with category tabs), latest writing and testimonials. Sections that list things show a count. Sections without data are left out.

The columns sit side by side from 64rem of container width. On narrower containers the identity comes first and the widgets last.

Owner view. Pass onEdit or editHref when the member is looking at their own page. "Edit profile" replaces the contact button, the closing call to action is left out, and two private sections appear: Apps (the products they use, with the official mark, plan, role, organisation and last use) and Account (email, language, time zone, member since, whatever details you pass). A member with no apps sees an empty state with one "Browse apps" action, never an empty grid.

When to use

  • A portfolio, a personal site or a team-member page.

When not to use

  • An in-app user card: use ProfileCard.
  • A company landing page: use the marketing blocks.

Import

import { ProfilePage } from "@fadymondy/nasaq/web";

Quick start

<ProfilePage profile={profile} blogHref="/blog" postHref={(p) => `/blog/${p.slug}`} onContact={openForm} />

The owner's view of the same page:

<ProfilePage
  profile={profile}
  editHref="/settings/profile"
  apps={[{ id: "mahaam", name: "Mahaam", icon: mahaamMark, href: "https://mahaam.app", plan: "Pro", role: "Owner", org: "3x1", lastUsed: "2026-09-28" }]}
  appsHref="/apps"
  account={[
    { label: "Email", value: "[email protected]" },
    { label: "Member since", value: "March 2024" },
  ]}
/>

Anatomy

ProfilePage           data-slot="profile-page"
├─ ProfileSidebar     Avatar, name, @handle, headline, details, one action, SocialLinks, bio, joined
├─ content column     children, ProfileApps*, ProfileAccount*, ProfileAbout, ProfileExperience,
│                     ProfileSkills, ProfileProjects, ProfileWriting, ProfileTestimonials
├─ aside              LocalClock, WeatherWidget, StatsWidget, NowWidget (under the sidebar)
└─ ProfileContact     visitors only
                      * owner only

API

ProfilePage

PropTypeDefaultDescription
profileProfileDatarequiredAll content.
onContact?() => voidmailtoContact button handler. Without it the button opens mailto: to profile.email.
onEdit? / editHref?() => void / stringnoneEither one turns on the owner view: "Edit profile" replaces contact, apps and account show, the closing call to action is left out.
apps?ProfileApp[]noneOwner only. The products they use. An empty array shows the empty state.
appsHref?stringnoneOwner only. Where "Browse apps" goes.
account?ProfileAccountDetail[]noneOwner only. { label, value } rows for the account section.
children?ReactNodenoneYour own sections, shown first in the content column. Use ProfileSection.
postHref? / blogHref?function / stringnoneLinks for the writing section.
now?Date | numberreal timeFreeze the clock and tenure.
widgets?booleantrueShow the widgets column.
labels?Partial<ProfilePageLabels>localeOverride strings.

ProfileData fields: name, handle, headline, bio, joined (ISO date), avatar, location, timeZone, workingHours, availability (open, limited, closed), availabilityNote, about (Markdown), email, links, experience (ProfileJob[]), skills, projects (ProfileProject[]), posts, testimonials, stats, now, nowUpdated, weather, cvHref.

Sections

ProfileSection takes title, description, action and count (shown as a badge beside the title). ProfileApp is a Product (from product switcher) plus role, org, plan and lastUsed. ProfileAccountDetail is { label, value }.

ProfileSidebar (the identity column; takes profile, onContact, onEdit, editHref, action to replace the button, and children below the bio), ProfileApps (apps, browseHref), ProfileAccount (details, description), ProfileHero (the older single-column header), ProfileSection, ProfileAbout, ProfileExperience, ProfileSkills, ProfileProjects, ProfileWriting, ProfileTestimonials and ProfileContact are exported so you can compose your own order. Each takes its data, optional labels and section props.

Model helpers

Exported from personal-widgets: tenureBetween, totalExperience (merges overlapping roles), groupSkills, distinct, isWorkingNow, offsetHours, partsIn, isKnownTimeZone.

Accessibility

Each section is a <section> labelled by its heading; there is one h1, the name in the sidebar. Counts are part of the heading text for screen readers. Each app card is one link. Project tabs follow the tabs pattern. The project cards use one link each. Availability is text, the dot is decoration.

RTL & i18n

English and Arabic strings, overridable. Names and companies are wrapped in <bdi> so mixed-script text does not reorder. Latin digits.

Styling

Container queries, tokens only. Avatar size is a class on Avatar.

Do / Don't

  • Do give timeZone if you want the clock; leave it out to hide it.
  • Do keep the headline to one sentence.
  • Do show app marks as they are: ProductIcon renders the official mark, never a generic icon or a recoloured one.
  • Don't show apps or account details to visitors. They only render in the owner view.
  • Don't put more than about five links in the hero.
  • The page does not generate a PDF. The CV button links to a file you provide with cvHref.

PersonalWidgets, BlogIndex, Timeline, FeatureStory.

Lab

Story Components/Website/Profile Page (Default, Owner View, Owner View Arabic, Owner View New) and page story Pages/Public/Profile.

On this page