# Profile screen

> A complete social profile: the handle in the bar, an avatar with a story ring that sweeps to seen, post, follower and following counts that roll up from zero as the screen opens, bio, location and link, Follow and Message buttons, and swipeable tabs with a photo grid that opens full screen and a list.

- Type: Component (screens)
- Access: Pro
- Page: https://goodui.dev/native/components/profile-screen
- Markdown: https://goodui.dev/native/components/profile-screen.md
- Registry item: https://goodui.dev/native/r/profile-screen.json
- Dependencies: react-native-svg
- Keywords: react native profile screen, user profile page, instagram style profile, expo social app template, follow button, photo grid tabs, story ring avatar

## When to use

- Another person's profile in a social, community, creator or marketplace app.
- A public page for a host, coach or seller with their photos and a list of what they offer.

## When not to use

- Editing your own profile: use edit-profile-screen.
- Account and app settings: use settings-screen.
- Hundreds of photos: the grid is plain views inside a ScrollView; page the photos or move the grid into a FlatList with numColumns.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add @goodui-native-pro/profile-screen
npx expo install --fix
```

Pro: add `"@goodui-native-pro": { "url": "https://goodui.dev/native/r/pro/{name}.json", "headers": { "Authorization": "Bearer ${GOODUI_LICENSE_KEY}" } }` to the `registries` of components.json and put your license key in .env.local.

The component lands in `components/goodui-native/profile-screen/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs avatar, button, empty-state, icon-button, image-viewer, list-row, locale, rolling-number, toast, top-tabs.

### Setup

- Wrap the app in GestureHandlerRootView and ThemeProvider, and render <Toaster /> once for the follow error toast.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
// app/profile/[username].tsx
import { Stack, router, useLocalSearchParams } from "expo-router";
import { ProfileScreen } from "@/components/goodui-native/profile-screen/profile-screen";
import { api, useProfile } from "@/lib/social";

export default function ProfileRoute() {
  const { username } = useLocalSearchParams<{ username: string }>();
  const { user, stats, photos, trips, isFollowing } = useProfile(username);
  return (
    <>
      <Stack.Screen options={{ headerShown: false }} />
      <ProfileScreen
        user={{ name: user.name, username: user.username, bio: user.bio, avatar: user.photoUrl, location: user.city, story: user.hasNewStory ? "unseen" : undefined }}
        stats={stats}
        photos={photos.map((p) => ({ source: p.url, width: p.width, height: p.height, alt: p.description }))}
        items={trips.map((t) => ({ id: t.id, title: t.place, subtitle: `${t.photoCount} photos`, value: t.month }))}
        defaultFollowing={isFollowing}
        onFollowChange={(following) => (following ? api.follow(username) : api.unfollow(username))}
        onMessage={() => router.push(`/chat/${user.id}`)}
        onStoryPress={() => router.push(`/stories/${username}`)}
        onItemPress={(item) => router.push(`/trips/${item.id}`)}
        onBack={() => router.back()}
      />
    </>
  );
}
```

## Examples

### On its own, with a sample profile (the grid shows its empty state without photos)

```tsx
<ProfileScreen />
```

### Bundled photos and custom tab titles

```tsx
<ProfileScreen
  photos={[
    { source: require("@/assets/lake.jpg"), alt: "A cold lake at sunrise", width: 1200, height: 900 },
    { source: require("@/assets/ridge.jpg"), alt: "A ridge trail above the clouds" },
  ]}
  tabTitles={{ photos: "Shots", items: "Routes" }}
/>
```

## API reference

### ProfileScreen

`import { ProfileScreen } from "@/components/goodui-native/profile-screen/profile-screen";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `user` | `ProfileUser` | `sampleProfileUser` | – |
| `stats` | `ProfileStats` | `sampleProfileStats` | Counts under the avatar. They count up from zero when the screen opens. |
| `photos` | `ProfilePhoto[]` | `[]` | Photos in the grid tab. Tapping one opens it full screen. |
| `items` | `ProfileListItem[]` | `sampleProfileItems` | Rows in the second tab. |
| `tabTitles` | `{ photos: string; items: string }` | `{ photos: "Photos", items: "Trips" }` | Tab titles. Default "Photos" and "Trips". |
| `defaultFollowing` | `boolean` | `false` | Whether you follow this person when the screen opens. |
| `onFollowChange` | `(following: boolean) => unknown` | – | Follow was toggled. The button and the follower count change at once; return a promise and a rejection rolls both back with an error toast. |
| `onMessage` | `() => void` | – | Shows a Message button beside Follow. |
| `onStoryPress` | `() => void` | – | Tapping the avatar while it has a story ring. The ring then sweeps to seen. |
| `onItemPress` | `(item: ProfileListItem) => void` | – | Tapping a row in the second tab. |
| `onBack` | `() => void` | – | Shows a back button in the bar. |
| `onMore` | `() => void` | – | Shows a more button in the bar, for example to open an action sheet. |
| `locale` | `string` | – | BCP 47 locale for the counts. Defaults to the device locale. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- The handle in the bar is a header. The avatar reads the name and 'new story' while the ring is unseen; with onStoryPress it is a button.
- Counts are real numbers for screen readers (no digit strips are read), each followed by its label.
- Follow is a button named 'Follow Aiko Tanaka' or 'Following Aiko Tanaka'; the change is announced.
- Tabs are a tab list with selected state; every photo is a button labelled with its alt text and opens the viewer, which supports the arrow keys and Escape on the web.
- On the web the scrolling profile is a named region that takes keyboard focus.

## Motion

- The counts roll up from zero digit by digit a moment after the screen opens; following rolls the follower count by one, up or down with the change.
- Tapping an unseen story ring sweeps the accent arc away to the seen ring.
- Photos fly out of their grid cell into the full-screen viewer and back on close; the tab indicator stretches between tabs as you swipe.
- Reduced motion: counts change in place, the ring crossfades instead of sweeping, photos fade in and out instead of flying.

## Haptics

- tap on Follow, Message, back and more
- tap on the avatar when it opens stories
- select when the tab changes

## Performance

- Digit rolls, the ring sweep, the tab indicator and the viewer run on the UI thread.
- Tab pages mount lazily: a page mounts once it or a neighbour is current, then stays mounted.

## Notes for AI

- Pass photos only as image sources through props (URLs or require() from your app); the screen bundles no images. Without photos the grid tab shows an empty state.
- stats are plain numbers; the screen formats them with Intl (pass locale to change separators) and adds or removes one follower when the person toggles Follow.
- Return a promise from onFollowChange; a rejection rolls the button and the count back and shows an error toast, so do not add your own loading state.
- Buttons appear only for the callbacks you pass: onBack, onMore, onMessage, onStoryPress (makes the avatar tappable), onItemPress.
- Render it full-bleed with the navigator header hidden (headerShown: false); it handles the safe areas itself.

## Related

- [Avatar](https://goodui.dev/native/components/avatar.md): A photo that fades in over initials on a color picked from the name, with a presence dot and a story ring whose accent arc sweeps in when there is something new and away once seen; AvatarGroup stacks people with a +N counter and fans out on tap.
- [Rolling number](https://goodui.dev/native/components/rolling-number.md): A number whose digits roll in the direction of change, with digits that open and close in place and currency signs that glide instead of jumping.
- [Top tabs](https://goodui.dev/native/components/top-tabs.md): A swipeable pager under a tab strip whose indicator tracks the swipe continuously: the leading edge runs ahead and the trailing edge follows, so it stretches between tabs and gathers as the page lands. Labels recolor with the swipe and a scrolling strip keeps the indicator centred.
- [Image viewer](https://goodui.dev/native/components/image-viewer.md): Tap a thumbnail and the photo flies out of it into a full-screen viewer, its crop opening up as it lands; pinch or double-tap to zoom at your fingers, drag to pan, swipe between photos, and drag down to send it back into its thumbnail while the background fades. On the web the wheel zooms toward the pointer.
- [Edit profile screen](https://goodui.dev/native/components/edit-profile-screen.md): Edit your own profile: change the photo from an action sheet (take, choose, remove) and watch the new one spring in, fields that validate as you leave them, Save with its own pending, saved and error states riding on the keyboard, and an alert that guards unsaved changes on back.
- [Chat screen](https://goodui.dev/native/components/chat-screen.md): A complete one-to-one chat thread: a header with the contact's avatar, presence and call buttons, messages grouped by day and by sender, receipts that tick forward, typing dots, long-press reactions and a composer that rides the keyboard; a sent message springs up out of the composer and ticks from clock to read.

## Guidance for AI tools

Profile screen: A complete social profile: the handle in the bar, an avatar with a story ring that sweeps to seen, post, follower and following counts that roll up from zero as the screen opens, bio, location and link, Follow and Message buttons, and swipeable tabs with a photo grid that opens full screen and a list. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
