bluesky-social/social-app
May 5, 2026
Generated by Inkwell Forge — automated codebase documentation analysis. Subject matter expert review is recommended before distribution.
This Component Library Reference covers 131 screens of the social-app React Native application. Screens assessed include: Inbox (/messages/inbox), Shell (/search/shell), Feed (/src/view/com/feeds/feed), Images (/onboarding/step-finished/images), Choose Account Form (/login/choose-account-form), Status Bar Shadow (/profile/header/status-bar-shadow), Shell (/profile/header/shell), Handle Suggestions (/signup/step-handle/handle-suggestions), Miscellaneous Notification Settings, Account Settings, Settings (/messages/settings), Utils (/search/utils), Mention Notification Settings, Settings (/settings), Deactivated (/deactivated), Log (/log), Hashtag (/hashtag), No Saved Feeds Of Any Type, No Following Feed, No Feeds Pinned, Find Contacts Flow, List Hidden, Shared Preferences Tester, Form Container, Set New Password Form, State (/onboarding/state), Login Form, Chat List, Emoji Picker, Value Proposition Pager.shared, Gif, Password Updated Form, Verification Settings, Forgot Password Form, Value Proposition Pager, Expo Scroll Forwarder, Index.web (onboarding/step-find-contacts-intro), Index.web (onboarding/step-find-contacts), Activity List, Layout (/onboarding/layout), Conversation, Avatar Creator Items, Edit Profile Dialog, Growable Banner, Starter Pack Card, Feed Section, Types (/onboarding/step-profile/types), Const (/post-thread/const), Value Proposition Pager.web, Interest Button, Metrics, Handle, Growable Avatar, Avatar Circle, Util (/onboarding/util), Post Liked By, Status Bar Shadow.web, Profile Labeler Liked By, Explore Trending Videos, Explore Trending Topics, Suggested Follows, Profile Followers, Profile Follows, Known Followers, Profile Search, Explore Recommendations, Feed (/profile/sections/feed), Explore Suggested Accounts, Labels (/profile/sections/labels), Search Results, Saved Feeds, Types (/profile/sections/types), Explore Interests Card, Explore (/search/explore), App Passwords, Activity Privacy Settings, External Media Preferences, Index.web (app-icon-settings), Settings List Item.web, App Icon Image, Settings List Item (app-icon-settings), Types (app-icon-settings), Automation Label Settings, Accessibility Settings, Appearance Settings, About Settings, Interests Settings, Content And Media Settings, Find Contacts Settings, Following Feed Preferences, Legacy Notification Settings, Reposts On Reposts Notification Settings, New Follower Notification Settings, Reply Notification Settings, Quote Notification Settings, Privacy And Security Settings, Like Notification Settings, Thread Preferences, Likes On Reposts Notification Settings, Repost Notification Settings, Splash, Starter Pack, Gesture Action, Signup Queued, Draggable Scroll, Takendown, Index.web (video-feed), Error, Back Next Buttons, Step Details, Captcha Web, Types (video-feed), State (/signup/state), State (/starter-pack/wizard/state), Starter Pack Landing, Topic, Step Profiles, Step Feeds, Policies, Captcha Web View.web, Placeholder Canvas, Display Name, Post Quotes, Avatar Creator Circle, Activity Notification Settings, Profile Header Standard, Post Reposted By, Language Settings, Error State, About Section, Profile Header Labeler. Components not referenced in the assessed documentation are outside the scope of this document.
Generated by Inkwell Forge — automated codebase documentation analysis. Subject matter expert review is recommended before distribution.
This reference covers all reusable UI components observable from the assessed screen documentation. The application is built on React Native and Expo, using a custom internal design system (#/alf) for atomic styles and theming. No single third-party UI component library (such as React Native Paper or NativeBase) is used; instead, the application relies on a bespoke component library under #/components/*. Third-party packages include react-native-gesture-handler, react-native-reanimated, expo-image, expo-blur, expo-linear-gradient, react-native-webview, react-native-view-shot, and react-native-keyboard-controller.
Component hierarchy:
View, Text, TextInput, ScrollView, Pressable, Modal, ActivityIndicator. These are not individually documented unless wrapped in a shared component.#/components/* and #/view/com/*. These are the primary subject of this document.GestureDetector from react-native-gesture-handler, Animated.View from react-native-reanimated).React Native vs. web differences: React Native components render to native views, not HTML DOM elements. View ≠ div, Text ≠ span, StyleSheet ≠ CSS classes. All layout uses Flexbox with flexDirection defaulting to 'column'. The application uses .web.tsx / .ios.tsx / .android.tsx file splits for several components (e.g., StatusBarShadow, ValuePropositionPager, CaptchaWebView, ExpoScrollForwarder). These are documented as separate variants of the same logical component.
Platform-specific variants: The following logical components have documented platform splits: StatusBarShadow (iOS animated vs. web null), ValuePropositionPager (native PagerView vs. web single-page), CaptchaWebView (native WebView vs. web iframe), ExpoScrollForwarderView (native passthrough vs. web null), AppIconSettings (native only; web throws), StepFindContactsIntro / StepFindContacts (native only; web throws).
How to use this document: Use the Component Inventory table for quick lookup by component name, then navigate to the full entry using the component ID (C-XX).
| # | Component Name | Category | Library | Platform | Summary | Screens Using It |
|---|---|---|---|---|---|---|
| C-01 | Layout.Screen |
Layout | Custom | iOS + Android | Root screen container providing safe area and background | All settings, profile, messages, search, onboarding screens |
| C-02 | Layout.Header.Outer |
Layout | Custom | iOS + Android | Horizontal header bar container | All screens with a header |
| C-03 | Layout.Header.BackButton |
Navigation | Custom | iOS + Android | Back navigation button rendered in the header left slot | Most stack screens |
| C-04 | Layout.Header.Content |
Layout | Custom | iOS + Android | Center content area of the header bar | Most screens with a title |
| C-05 | Layout.Header.TitleText |
Data Display | Custom | iOS + Android | Styled heading text inside the header | Most screens |
| C-06 | Layout.Header.SubtitleText |
Data Display | Custom | iOS + Android | Secondary text below the title in the header | Post Liked By, Post Reposted By, Post Quotes, Hashtag |
| C-07 | Layout.Header.Slot |
Layout | Custom | iOS + Android | Empty slot for layout balance in the header | Most screens |
| C-08 | Layout.Header.MenuButton |
Navigation | Custom | iOS + Android | Hamburger menu button for the left header slot | Search Shell, Chat List |
| C-09 | Layout.Content |
Layout | Custom | iOS + Android | Scrollable content area below the header | All settings, profile, auth screens |
| C-10 | Layout.Center |
Layout | Custom | iOS + Android | Horizontally centered content container with optional side borders | Error, No Feeds Pinned, List Hidden, Deactivated |
| C-11 | SafeAreaView (via useSafeAreaInsets) |
Layout | react-native-safe-area-context |
iOS + Android | Provides device safe area inset values | StatusBarShadow, GrowableBanner, Signup Queued, Deactivated |
| C-12 | KeyboardAwareScrollView |
Layout | react-native-keyboard-controller |
iOS + Android | Scroll container that adjusts for the on-screen keyboard | Takendown, Step Profiles, Step Feeds, Login Form |
| C-13 | StatusBarShadow |
Layout | Custom | iOS (animated) / Web (null) | Gradient overlay at the top of the screen covering the status bar area | Profile Header Shell |
| C-14 | GrowableBanner |
Layout | Custom | iOS (animated) / Android (static) | Profile banner that scales and blurs on iOS overscroll | Profile Header Shell |
| C-15 | GrowableAvatar |
Layout | Custom | iOS (animated) / Android (static) | Profile avatar that scales on iOS overscroll | Profile Header Shell |
| C-16 | FormContainer |
Layout | Custom | iOS + Android | Login flow step wrapper with title, gap, and responsive gutters | Login Form, Forgot Password Form, Set New Password Form, Password Updated Form, Choose Account Form |
| C-17 | List |
Data Display | Custom | iOS + Android | Virtualized scrollable list wrapping Animated.FlatList |
Inbox, Chat List, Feed, Hashtag, Profile Followers, Profile Follows, Known Followers, Search Results, Saved Feeds, Activity List, Topic, Step Profiles, Step Feeds, Feed Section, Labels |
| C-18 | ListFooter |
Data Display | Custom | iOS + Android | Bottom-of-list component showing pagination loading or error | Inbox, Chat List, Hashtag, Profile Followers, Profile Follows, Known Followers, Search Results, Activity List, Topic, Activity Notification Settings |
| C-19 | ListMaybePlaceholder |
Data Display | Custom | iOS + Android | Conditional placeholder showing loading, error, or empty state | Hashtag, Profile Followers, Profile Follows, Known Followers, Starter Pack, Starter Pack Landing, Topic |
| C-20 | PostFeed |
Data Display | Custom | iOS + Android | Animated FlatList of posts with interstitials, pull-to-refresh, and infinite scroll | Feed, Feed Section, Activity List |
| C-21 | Post |
Data Display | Custom | iOS + Android | Individual post card with author, content, embeds, and action controls | Feed, Hashtag, Topic, Activity List |
| C-22 | FeedCard.Default |
Data Display | Custom | iOS + Android | Feed generator card with name, description, and subscribe action | Search Results, Explore, Starter Pack Landing, Step Feeds |
| C-23 | ProfileCard.Default |
Data Display | Custom | iOS + Android | Full profile card with avatar, name, handle, follow button, and description | Explore, Starter Pack Landing |
| C-24 | ProfileCard.Avatar |
Data Display | Custom | iOS + Android | User avatar within a profile card | Explore, Activity Notification Settings |
| C-25 | ProfileCard.NameAndHandle |
Data Display | Custom | iOS + Android | Display name and handle within a profile card | Explore, Activity Notification Settings |
| C-26 | ProfileCard.FollowButton |
Form | Custom | iOS + Android | Follow/unfollow button within a profile card | Explore, Activity Notification Settings |
| C-27 | ProfileCard.Description |
Data Display | Custom | iOS + Android | Bio text within a profile card | Explore |
| C-28 | ProfileCard.Link |
Navigation | Custom | iOS + Android | Navigation link wrapping a profile card | Explore, Search Results |
| C-29 | StarterPackCard |
Data Display | Custom | iOS + Android | Starter pack card with avatar stack, name, description, and follow-all button | Onboarding Step Suggested Starterpacks, Explore |
| C-30 | ChatListItem |
Data Display | Custom | iOS + Android | Conversation row with avatar, name, last message, unread indicator, and swipe actions | Inbox, Chat List |
| C-31 | WizardProfileCard |
Data Display | Custom | iOS + Android | Profile card with checkbox for wizard multi-select | Step Profiles |
| C-32 | WizardFeedCard |
Data Display | Custom | iOS + Android | Feed card with checkbox for wizard multi-select | Step Feeds |
| C-33 | RichText |
Data Display | Custom | iOS + Android | Renders AT Protocol rich text with facets (links, mentions, tags) | Feed, Profile Header Standard, Profile Header Labeler, Conversation |
| C-34 | Text (Typography) |
Data Display | Custom | iOS + Android | Themed text component with emoji support | All screens |
| C-35 | EmptyState |
Data Display | Custom | iOS + Android | Centered icon + message for empty list states | Feed Section, No Feeds Pinned, No Saved Feeds, Hashtag, App Passwords, Log |
| C-36 | Admonition |
Feedback | Custom | iOS + Android | Styled callout box for info, tip, warning, or error messages | Settings screens, Notification Settings, Verification Settings, Policies, Explore Interests Card |
| C-37 | CharProgress |
Data Display | Custom | iOS + Android | Character count progress indicator overlaid on a text field | Takendown, Step Details |
| C-38 | AvatarStack |
Data Display | Custom | iOS + Android | Horizontal stack of overlapping profile avatars | Inbox, Chat List, Explore Trending Topics, Starter Pack Card |
| C-39 | ProfileBadges |
Data Display | Custom | iOS + Android | Inline verification or role badges next to a display name | Inbox, Profile Header Standard, Profile Header Labeler |
| C-40 | KnownFollowers |
Data Display | Custom | iOS + Android | Mutual follower avatars and count shown on a profile or request | Inbox, Profile Header Standard |
| C-41 | LoadLatestBtn |
Navigation | Custom | iOS + Android | Floating button to scroll to top or load new posts | Feed, Feed Section, About Section |
| C-42 | Loader |
Feedback | Custom | iOS + Android | Animated loading spinner icon | All screens with loading states |
| C-43 | Skeleton (LoadingPlaceholder) |
Feedback | Custom | iOS + Android | Shimmer placeholder for loading content | Chat List, Explore, Notification Settings |
| C-44 | TextField.Root |
Form | Custom | iOS + Android | Styled container for a text input field with validation state | Login Form, Forgot Password Form, Set New Password Form, Takendown, Step Details, Edit Profile Dialog, App Passwords |
| C-45 | TextField.Input |
Form | Custom | iOS + Android | The actual text input element within a TextField.Root |
Login Form, Forgot Password Form, Set New Password Form, Takendown, Step Details, App Passwords |
| C-46 | TextField.LabelText |
Form | Custom | iOS + Android | Visible label above a text field | Login Form, Forgot Password Form, Set New Password Form, Step Details, App Passwords |
| C-47 | TextField.Icon |
Form | Custom | iOS + Android | Leading icon inside a text field | Login Form, Forgot Password Form |
| C-48 | TextField.SuffixText |
Form | Custom | iOS + Android | Right-aligned suffix text inside a text field (e.g., character counter) | Step Details |
| C-49 | Toggle.Group |
Form | Custom | iOS + Android | Container managing a group of checkbox or radio toggle items | Notification Settings screens, Settings (/messages/settings), Appearance Settings, Language Settings, Following Feed Preferences |
| C-50 | Toggle.Item |
Form | Custom | iOS + Android | Individual selectable item within a Toggle.Group |
Notification Settings screens, Settings (/messages/settings), Appearance Settings, Language Settings |
| C-51 | Toggle.Platform |
Form | Custom | iOS + Android | Platform-appropriate toggle control (Switch on native, checkbox on web) | Notification Settings screens, Accessibility Settings, External Media Preferences |
| C-52 | Toggle.Radio |
Form | Custom | iOS + Android | Radio button indicator within a Toggle.Item |
Notification Settings screens, Settings (/messages/settings) |
| C-53 | Toggle.LabelText |
Form | Custom | iOS + Android | Styled label text within a Toggle.Item |
Notification Settings screens, Settings (/messages/settings), Appearance Settings |
| C-54 | Select.Root |
Form | Custom | iOS + Android | Dropdown/select control for single-value selection | Language Settings |
| C-55 | SearchInput |
Form | Custom | iOS + Android | Search text input with clear button and optional hotkey support | Search Shell, Step Profiles, Step Feeds |
| C-56 | SegmentedControl.Root |
Form | Custom | iOS + Android | Radio-style segmented button group | Appearance Settings |
| C-57 | SegmentedControl.Item |
Form | Custom | iOS + Android | Individual segment within a SegmentedControl.Root |
Appearance Settings |
| C-58 | Toast |
Feedback | Custom | iOS + Android | Transient notification message shown at the bottom of the screen | Account Settings, Chat List, Inbox, Saved Feeds, Settings, Automation Label Settings |
| C-59 | Prompt.Basic |
Feedback | Custom | iOS + Android | Simple confirmation dialog with title, description, and confirm/cancel buttons | Account Settings, App Passwords, Saved Feeds, List Hidden, Profile Header Standard |
| C-60 | Prompt.Outer |
Feedback | Custom | iOS + Android | Outer wrapper for a prompt dialog, used with Prompt.Basic |
Account Settings, Profile Header Labeler |
| C-61 | ErrorScreen |
Feedback | Custom | iOS + Android | Full-page error display with icon, title, message, optional details, and retry button | Error, Find Contacts Flow, App Icon Settings (web), Captcha Web, Starter Pack Landing |
| C-62 | FormError |
Feedback | Custom | iOS + Android | Inline error message displayed within a form | Login Form, Forgot Password Form, Set New Password Form |
| C-63 | TabBar |
Navigation | Custom | iOS + Android | Horizontal tab label row rendered above a Pager |
Search Results, Hashtag, Topic, Starter Pack, Profile screens |
| C-64 | Pager |
Navigation | Custom | iOS + Android | Swipeable horizontal tab container | Search Results, Hashtag, Topic, Starter Pack, Profile screens |
| C-65 | PagerWithHeader |
Navigation | Custom | iOS + Android | Tabbed pager with a sticky header and synchronized scroll | Starter Pack, Profile screens |
| C-66 | ProfileMenu |
Navigation | Custom | iOS + Android | Three-dot overflow menu for profile actions | Profile Header Standard, Profile Header Labeler |
| C-67 | OverflowMenu (ConvoMenu) |
Navigation | Custom | iOS + Android | Context menu for conversation actions (mute, block, report, leave) | Inbox, Chat List |
| C-68 | SettingsList.LinkItem |
Navigation | Custom | iOS + Android | Tappable settings row that navigates to another screen | Settings, Account Settings, Content And Media Settings, Privacy And Security Settings, About Settings |
| C-69 | SettingsList.PressableItem |
Navigation | Custom | iOS + Android | Tappable settings row that triggers an action | Settings, Account Settings, About Settings |
| C-70 | GestureActionView |
Gesture & Touch | Custom | iOS + Android | Horizontal swipe gesture wrapper revealing action buttons at configurable thresholds | Inbox, Chat List |
| C-71 | DraggableScrollView |
Gesture & Touch | Custom | Web | Horizontal ScrollView with click-and-drag scrolling for desktop |
Explore Trending Videos |
| C-72 | AnimatedPressable |
Gesture & Touch | Custom | iOS + Android | Pressable with Reanimated-driven opacity/scale animation | Starter Pack Landing |
| C-73 | SubtleHover |
Gesture & Touch | Custom | iOS + Android | Low-opacity hover/press highlight overlay | Explore Trending Topics, Explore Suggested Accounts |
| C-74 | BlockDrawerGesture |
Gesture & Touch | Custom | iOS + Android | Wrapper that prevents the navigation drawer from intercepting horizontal swipes | Explore Trending Videos, Explore Suggested Accounts |
| C-75 | Dialog.Outer |
Utility | Custom | iOS + Android | Root dialog container with native/web presentation options | Account Settings, App Passwords, Edit Profile Dialog, Conversation |
| C-76 | Dialog.ScrollableInner |
Utility | Custom | iOS + Android | Scrollable content area within a dialog with header slot | Account Settings, App Passwords, Edit Profile Dialog |
| C-77 | Dialog.Input |
Utility | Custom | iOS + Android | Styled text input for use inside a dialog | Account Settings, App Passwords |
| C-78 | Portal / createPortalGroup |
Utility | Custom | iOS + Android | Teleports child content to a different position in the component tree | Onboarding Layout, SettingsList.Group |
| C-79 | ScreenTransition |
Utility | Custom | iOS + Android | Directional slide/fade animation wrapper for wizard step transitions | Find Contacts Flow, Starter Pack Wizard steps |
| C-80 | GifView |
Utility | expo-bluesky-gif-view (Custom Expo Module) |
iOS + Android | Native GIF playback component with play/pause/toggle control | Conversation (inferred from module documentation) |
| C-81 | EmojiPicker |
Utility | expo-emoji-picker (Custom Expo Module) |
iOS + Android | Native emoji picker view that fires onEmojiSelected |
Conversation (inferred from module documentation) |
| C-82 | ExpoScrollForwarderView |
Utility | expo-scroll-forwarder (Custom Expo Module) |
iOS + Android (native passthrough) / Web (null) | Forwards scroll/gesture events from a non-scrollable area to a target ScrollView |
Profile screens (inferred from module documentation) |
| C-83 | AgeRestrictedScreen |
Utility | Custom | iOS + Android | Wrapper that gates content behind age assurance, showing a restricted notice if not met | Inbox, Chat List, Conversation |
| C-84 | SettingsList.Container |
Utility | Custom | iOS + Android | Full-width vertical list wrapper with vertical padding | All settings screens |
| C-85 | SettingsList.Group |
Utility | Custom | iOS + Android | Grouped settings section using Portal to render icon/title in a header row | Settings screens with grouped items |
| C-86 | SettingsList.Item |
Utility | Custom | iOS + Android | Horizontal row layout for a single settings entry | All settings screens |
| C-87 | SettingsList.Divider |
Utility | Custom | iOS + Android | Horizontal rule separator between settings groups | All settings screens |
| C-88 | PreferenceControls |
Utility | Custom | iOS + Android | Reusable toggle controls for notification channel and filter preferences | All notification settings screens |
| C-89 | ItemTextWithSubtitle |
Utility | Custom | iOS + Android | Title + subtitle text block used within a settings item | Notification Settings screens |
| C-90 | InlineLinkText |
Utility | Custom | iOS + Android | Tappable hyperlink rendered inline within a text block | No Following Feed, No Feeds Pinned, Policies, Verification Settings, Privacy And Security Settings, Language Settings |
| C-91 | Link |
Navigation | Custom | iOS + Android | Navigation link component rendering as anchor on web and using React Navigation on native | Feed, Search, Profile, Starter Pack, Hashtag |
| C-92 | Button |
Gesture & Touch | Custom | iOS + Android | Pressable button with variant, color, size, shape, and label props | All interactive screens |
| C-93 | ButtonIcon |
Utility | Custom | iOS + Android | Icon slot within a Button component |
All screens with icon buttons |
| C-94 | ButtonText |
Utility | Custom | iOS + Android | Text slot within a Button component |
All screens with text buttons |
| C-95 | Logo |
Data Display | Custom | iOS + Android | Bluesky icon mark SVG component | Splash, Signup Queued, Deactivated, Onboarding Layout, Starter Pack Landing |
| C-96 | Logotype |
Data Display | Custom | iOS + Android | Bluesky wordmark SVG component | Splash |
| C-97 | UserAvatar |
Data Display | Custom | iOS + Android | Profile picture with moderation, live status, and type (circle/rect) support | Inbox, Chat List, Settings, Profile Header Shell, Onboarding |
| C-98 | UserBanner |
Data Display | Custom | iOS + Android | Profile banner image with moderation and type (user/labeler) support | Profile Header Shell, Edit Profile Dialog |
| C-99 | EditableUserAvatar |
Data Display | Custom | iOS + Android | Avatar with an edit affordance for profile editing | Edit Profile Dialog |
| C-100 | LiveIndicator |
Data Display | Custom | iOS + Android | Animated live status indicator overlaid on an avatar | Profile Header Shell, Profile Header Standard, Profile Header Labeler |
Category: Layout
Library: Custom
Platform: iOS + Android
Summary: Root screen container that provides safe area handling, background color, and a consistent full-screen wrapper for all screens in the application.
When to Use:
When NOT to Use:
Dialog.Outer instead.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
testID |
string |
No | — | Test identifier for automated testing and accessibility tooling |
style |
StyleProp<ViewStyle> |
No | — | Additional styles applied to the root container |
children |
React.ReactNode |
Yes | — | Screen content |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Safe area | Respects notch, Dynamic Island, home indicator | Respects status bar and navigation bar | Uses react-native-safe-area-context internally |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Full-screen container with theme background color |
Composition:
<Layout.Screen testID="myScreen">
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>My Screen</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
{/* screen body */}
</Layout.Content>
</Layout.Screen>
Accessibility:
testID supports automated testing and accessibility tooling on React Native.Related Components:
Category: Layout
Library: Custom
Platform: iOS + Android
Summary: Horizontal header bar container that wraps the top navigation area of a screen, providing consistent height, border, and background styling.
When to Use:
Layout.Screen when a screen requires a top navigation bar.When NOT to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
noBottomBorder |
boolean |
No | false |
Suppresses the bottom border of the header |
children |
React.ReactNode |
Yes | — | Header content (back button, title, slot) |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Full-width bar with bottom border |
| No border | noBottomBorder={true} |
Bottom border is hidden |
Composition:
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>Title</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
Accessibility:
Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Back navigation button rendered in the left slot of the header bar, using React Navigation's goBack() behavior.
When to Use:
When NOT to Use:
Layout.Header.MenuButton instead.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onPress |
() => void |
No | navigation.goBack() |
Custom press handler; defaults to stack back navigation |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Left-pointing chevron or arrow icon |
Composition:
<Layout.Header.Outer>
<Layout.Header.BackButton />
{/* ... */}
</Layout.Header.Outer>
Accessibility:
accessibilityLabel such as "Go back" defined within the component implementation.Related Components:
Category: Layout
Library: Custom
Platform: iOS + Android
Summary: Center content area of the header bar, typically containing the title and optional subtitle.
When to Use:
Layout.Header.TitleText and Layout.Header.SubtitleText in the center of the header.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
align |
'center' | 'left' | 'platform' |
No | 'platform' |
Text alignment of the header content |
children |
React.ReactNode |
Yes | — | Title and subtitle content |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Center aligned | align="center" |
Title centered in the header |
| Left aligned | align="left" |
Title left-aligned |
| Platform default | align="platform" |
Center on mobile, left on tablet |
Composition:
<Layout.Header.Content align="center">
<Layout.Header.TitleText>Screen Title</Layout.Header.TitleText>
<Layout.Header.SubtitleText>42 items</Layout.Header.SubtitleText>
</Layout.Header.Content>
Accessibility:
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Styled heading text displayed in the center of the header bar, representing the screen's primary title.
When to Use:
Layout.Header.Content.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | The title string or translated content |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Bold, appropriately sized heading text |
Composition:
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Notifications</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
Accessibility:
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Secondary text displayed below the title in the header, typically showing a count or contextual detail.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | The subtitle string or translated content |
Composition:
<Layout.Header.Content>
<Layout.Header.TitleText>Liked By</Layout.Header.TitleText>
<Layout.Header.SubtitleText>
<Plural value={likeCount ?? 0} one="# like" other="# likes" />
</Layout.Header.SubtitleText>
</Layout.Header.Content>
Accessibility:
Related Components:
Category: Layout
Library: Custom
Platform: iOS + Android
Summary: Empty slot in the header used to balance the layout when only one side has a button, or to host a right-side action button.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
No | — | Optional content to render in the slot |
Composition:
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>Title</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot>
{/* optional right-side button */}
</Layout.Header.Slot>
</Layout.Header.Outer>
Accessibility:
Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Hamburger menu button rendered in the left slot of the header, used on root screens to open the navigation drawer.
When to Use:
When NOT to Use:
Layout.Header.BackButton instead.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onPress |
() => void |
No | — | Custom press handler; defaults to opening the navigation drawer |
Composition:
<Layout.Header.Outer>
<Layout.Header.MenuButton />
<Layout.Header.Content>
<Layout.Header.TitleText>Chats</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
Accessibility:
Related Components:
Category: Layout
Library: Custom
Platform: iOS + Android
Summary: Scrollable content area below the header, providing consistent padding and background for screen body content.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
scrollEnabled |
boolean |
No | true |
Whether the content area is scrollable |
ignoreTabletLayoutOffset |
boolean |
No | false |
Suppresses tablet-specific layout offset |
children |
React.ReactNode |
Yes | — | Screen body content |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
<Layout.Screen>
<Layout.Header.Outer>{/* ... */}</Layout.Header.Outer>
<Layout.Content>
<SettingsList.Container>
{/* settings items */}
</SettingsList.Container>
</Layout.Content>
</Layout.Screen>
Accessibility:
Related Components:
Category: Layout
Library: Custom
Platform: iOS + Android
Summary: Horizontally centered content container with optional side borders, used for error states, empty states, and narrow-column content.
When to Use:
sideBorders is needed to visually separate content from the screen edges on wide displays.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
sideBorders |
boolean |
No | false |
Renders vertical border lines on the left and right sides |
children |
React.ReactNode |
Yes | — | Content to center |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
<Layout.Center sideBorders>
<EmptyState
icon={<MessageIcon />}
message="No messages yet"
/>
</Layout.Center>
Accessibility:
Related Components:
useSafeAreaInsets)Category: Layout
Library: react-native-safe-area-context
Platform: iOS + Android
Summary: Provides device-specific safe area inset values (notch, home indicator, status bar) so UI elements can be positioned correctly on all device form factors.
When to Use:
useSafeAreaInsets() is used rather than the SafeAreaView component directly in most cases.When NOT to Use:
Layout.Screen — safe area handling is already provided by that component.Props:
The hook useSafeAreaInsets() returns { top, bottom, left, right } in logical pixels.
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Top inset | Notch / Dynamic Island height | Status bar height | Varies by device |
| Bottom inset | Home indicator height | Navigation bar height | 0 on devices without gesture navigation |
Composition:
const insets = useSafeAreaInsets()
<View style={{ paddingBottom: insets.bottom + 16 }}>
{/* fixed bottom content */}
</View>
Accessibility:
Related Components:
Category: Layout
Library: react-native-keyboard-controller
Platform: iOS + Android
Summary: Scroll container that automatically adjusts its content offset when the software keyboard appears, keeping focused inputs visible.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | Form content |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
contentContainerStyle |
StyleProp<ViewStyle> |
No | — | Styles for the scroll content container |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Keyboard avoidance | Scrolls content up | Scrolls content up | Both platforms supported by react-native-keyboard-controller |
Composition:
<KeyboardAwareScrollView>
<TextField.Root>
<TextField.Input label="Email" keyboardType="email-address" />
</TextField.Root>
<TextField.Root>
<TextField.Input label="Password" secureTextEntry />
</TextField.Root>
</KeyboardAwareScrollView>
Accessibility:
Related Components:
Category: Layout
Library: Custom
Platform: iOS (animated) / Web (null render)
Summary: Gradient overlay anchored to the top of the screen that visually separates the status bar from the profile banner content beneath it. On iOS with pager context, it animates with scroll position.
When to Use:
When NOT to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
topInset |
number |
Yes | — | Height of the gradient, matching the device's safe area top inset |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Animation | Translates upward on overscroll via Reanimated worklet | Static gradient | Driven by scrollY shared value from PagerHeaderContext |
| Web | Renders null |
— | .web.tsx variant returns null |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | No scroll | Black-to-transparent gradient at top of screen |
| Overscroll (iOS) | User pulls down past top | Gradient slides upward off-screen |
Composition:
<StatusBarShadow topInset={topInset} />
Accessibility:
accessibilityElementsHidden or importantForAccessibility is set in the documented source — this is a known gap.Known Issues:
StatusBarShadowInnner (three ns) — a typo in the source.Related Components:
Category: Layout
Library: Custom
Platform: iOS (animated) / Android (static)
Summary: Profile banner image container that scales up and applies a blur effect on iOS overscroll, creating a parallax/rubber-band effect. On Android and web, renders a static banner.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onPress |
() => void | undefined |
No | — | Handler for tapping the banner; opens lightbox when provided |
backButton |
React.ReactNode |
No | — | Back button node rendered over the banner |
isPlaceholderProfile |
boolean |
No | false |
Shows a loading placeholder instead of the banner image |
isFetching |
boolean |
No | false |
Shows a pull-to-refresh spinner when overscrolled |
bannerRef |
AnimatedRef<Animated.View> |
No | — | Ref for measuring the banner's screen position |
children |
React.ReactNode |
Yes | — | The banner image content |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Scale animation | Scales up from bottom on overscroll | No animation | Driven by scrollY shared value |
| Blur effect | AnimatedBlurView intensifies on overscroll |
No blur | expo-blur used on iOS only |
| Pull-to-refresh spinner | ActivityIndicator shown when overscrolled + fetching |
Not shown | useStickyToggle prevents flicker |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Normal scroll | Static banner image |
| Overscroll (iOS) | User pulls down | Banner scales up, blur intensifies |
| Loading | isPlaceholderProfile={true} |
LoadingPlaceholder shimmer |
| Fetching | isFetching={true} + overscrolled |
White ActivityIndicator spinner |
Composition:
<GrowableBanner
onPress={onPressBanner}
backButton={<BackButton />}
bannerRef={bannerRef}
isPlaceholderProfile={isPlaceholderProfile}
>
<UserBanner profile={profile} moderation={moderation} />
</GrowableBanner>
Accessibility:
Pressable wrapping the banner has accessibilityRole="image" and a dynamic accessibilityLabel.Known Issues:
// HACK: comment noting that scroll position reports 0 for one tick when fetching finishes; useStickyToggle with a 10ms delay is a workaround.Related Components:
Category: Layout
Library: Custom
Platform: iOS (animated) / Android (static)
Summary: Profile avatar container that scales up from its bottom-left corner on iOS overscroll. On Android and web, renders a static wrapper.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
style |
StyleProp<ViewStyle> |
No | — | Additional styles applied to the container |
children |
React.ReactNode |
Yes | — | The avatar image content |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Scale animation | Scales up from bottom-left on overscroll | No animation | transformOrigin: 'bottom left', driven by scrollY |
| Extrapolation | Clamped at right (no shrink on downward scroll) | — | Left extrapolation not clamped (potential scale > 1.2) |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Normal scroll | Static avatar at normal size |
| Overscroll (iOS) | User pulls down | Avatar scales up to 1.2× |
Composition:
<GrowableAvatar style={styles.avatar}>
<UserAvatar profile={profile} size={90} />
</GrowableAvatar>
Accessibility:
UserAvatar handles accessibility.Known Issues:
Related Components:
Category: Layout
Library: Custom
Platform: iOS + Android
Summary: Login flow step wrapper that provides a consistent vertical layout with a title (mobile only), gap spacing, and responsive horizontal gutters.
When to Use:
When NOT to Use:
Layout.Screen + Layout.Content for general screens.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
titleText |
string |
No | — | Title displayed at the top on mobile viewports only |
testID |
string |
No | — | Test identifier |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
children |
React.ReactNode |
Yes | — | Form content |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Mobile | !gtMobile |
Title rendered, horizontal gutters applied |
| Desktop | gtMobile |
Title suppressed, gutters not applied |
Composition:
<FormContainer titleText="Sign in" testID="loginForm">
<HostingProvider />
<TextField.Root>
<TextField.Input label="Username" />
</TextField.Root>
<Button label="Sign in" onPress={onPressNext}>
<ButtonText>Sign in</ButtonText>
</Button>
</FormContainer>
Accessibility:
titleText renders as a bold Text element on mobile; it is not explicitly marked as a heading, which may be a gap for screen reader heading navigation on web.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Virtualized scrollable list wrapping React Native's Animated.FlatList, with built-in pull-to-refresh, infinite scroll, and performance tuning props.
When to Use:
When NOT to Use:
View with .map() instead.DraggableScrollView or a horizontal ScrollView.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
data |
readonly T[] |
Yes | — | Array of items to render |
renderItem |
ListRenderItem<T> |
Yes | — | Function that renders each item |
keyExtractor |
(item: T, index: number) => string |
Yes | — | Unique key for each item |
onRefresh |
() => void |
No | — | Pull-to-refresh callback |
refreshing |
boolean |
No | false |
Whether pull-to-refresh is active |
onEndReached |
() => void |
No | — | Callback when scroll reaches near the end |
onEndReachedThreshold |
number |
No | — | Fraction of list height from end to trigger onEndReached |
ListHeaderComponent |
React.ComponentType | React.ReactElement |
No | — | Component rendered above the list |
ListFooterComponent |
React.ComponentType | React.ReactElement |
No | — | Component rendered below the list |
ListEmptyComponent |
React.ComponentType | React.ReactElement |
No | — | Component rendered when data is empty |
windowSize |
number |
No | — | Number of viewport-heights of items to keep rendered |
initialNumToRender |
number |
No | — | Number of items to render on initial mount |
desktopFixedHeight |
boolean |
No | — | Web-only: enables fixed-height scrollable container |
sideBorders |
boolean |
No | — | Renders side border lines |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
contentContainerStyle |
StyleProp<ViewStyle> |
No | — | Styles for the scroll content container |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
maxToRenderPerBatch |
20 (inferred) | 10 (inferred) | Tuned per platform in PostFeed |
removeClippedSubviews |
true |
true |
Removes off-screen views from native hierarchy |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Loading | data is empty, ListEmptyComponent shows spinner |
Spinner or skeleton |
| Empty | data is empty, no loading |
Empty state message |
| Populated | data has items |
Virtualized list of items |
| Refreshing | refreshing={true} |
Native pull-to-refresh spinner |
Composition:
<List
data={conversations}
renderItem={({ item }) => <ChatListItem convo={item} />}
keyExtractor={item => item.id}
onRefresh={onRefresh}
refreshing={isPTRing}
onEndReached={onEndReached}
onEndReachedThreshold={1.5}
ListFooterComponent={<ListFooter isFetchingNextPage={isFetchingNextPage} />}
windowSize={11}
/>
Accessibility:
keyExtractor using stable IDs (e.g., item.did, item.id) ensures correct accessibility tree reconciliation.keyboardShouldPersistTaps="handled" is set on some usages to ensure taps on interactive elements work while the keyboard is open.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Bottom-of-list component that shows a loading spinner during pagination or an error message with a retry button when a page fetch fails.
When to Use:
ListFooterComponent of a List that supports infinite scroll.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isFetchingNextPage |
boolean |
No | false |
Shows a loading spinner when true |
error |
string |
No | — | Error message to display with a retry button |
onRetry |
() => void |
No | — | Callback for the retry button |
hasNextPage |
boolean |
No | — | Whether more pages are available |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Loading | isFetchingNextPage={true} |
Centered ActivityIndicator spinner |
| Error | error is truthy |
Error message text with a retry button |
| End of list | !hasNextPage |
Transparent border spacer |
| Default | No special state | Transparent spacer |
Composition:
<List
data={items}
renderItem={renderItem}
keyExtractor={keyExtractor}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
/>
}
/>
Accessibility:
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Conditional placeholder component that renders a loading spinner, error state with retry, or empty state message depending on the provided props.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isLoading |
boolean |
No | false |
Shows a loading spinner |
isError |
boolean |
No | false |
Shows an error state |
error |
string |
No | — | Error message to display |
emptyType |
'results' | 'feed' |
No | — | Type of empty state message |
emptyMessage |
string |
No | — | Custom empty state message |
onRetry |
() => void |
No | — | Retry callback for error state |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Loading | isLoading={true} |
Centered spinner |
| Error | isError={true} |
Error message with retry button |
| Empty (results) | emptyType="results" |
"No results" message |
| Empty (feed) | emptyType="feed" |
Feed-specific empty message |
Composition:
{posts.length < 1 ? (
<ListMaybePlaceholder
isLoading={isLoading}
isError={isError}
error={cleanError(error)}
emptyType="results"
emptyMessage="We couldn't find any results for that topic."
onRetry={refetch}
/>
) : (
<List data={posts} renderItem={renderItem} keyExtractor={keyExtractor} />
)}
Accessibility:
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Animated FlatList of posts with heterogeneous row types (posts, interstitials, loading placeholders, error rows), pull-to-refresh, infinite scroll, and feed polling.
When to Use:
When NOT to Use:
List directly.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
feed |
FeedDescriptor |
Yes | — | Identifies which feed to load |
feedParams |
FeedParams |
No | — | Optional parameters for the feed query |
renderEmptyState |
() => JSX.Element |
No | — | Caller-provided empty state renderer |
renderEndOfFeed |
() => JSX.Element |
No | — | Caller-provided end-of-feed renderer |
onHasNew |
(v: boolean) => void |
No | — | Callback when new posts are detected |
onScrolledDownChange |
(v: boolean) => void |
No | — | Callback when scroll position changes |
scrollElRef |
ListRef |
No | — | Ref for programmatic scroll control |
headerOffset |
number |
No | 0 |
Offset for sticky header height |
isPageFocused |
boolean |
No | true |
Whether this feed page is currently visible |
isPageAdjacent |
boolean |
No | false |
Whether this feed page is adjacent to the focused page (enables prefetch) |
disablePoll |
boolean |
No | false |
Disables background polling for new posts |
enabled |
boolean |
No | true |
Whether the feed query is enabled |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
maxToRenderPerBatch |
20 | 1 | Tuned per platform |
onEndReachedThreshold |
2 | 2 | Triggers 2 viewport-heights before end |
| Scroll animation | Animated | Animated | Uses react-native-reanimated |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Loading | Initial fetch | PostFeedLoadingPlaceholder skeleton rows |
| Error | API failure | PostFeedErrorMessage with retry |
| Empty | No posts | Caller-provided renderEmptyState() |
| Populated | Posts available | Virtualized list of PostFeedItem rows |
| Load more error | Pagination failure | LoadMoreRetryBtn at bottom |
Composition:
<PostFeed
feed="following"
onHasNew={setHasNew}
onScrolledDownChange={setIsScrolledDown}
scrollElRef={scrollElRef}
renderEmptyState={() => <EmptyState message="No posts yet" />}
isPageFocused={isPageFocused}
/>
Accessibility:
accessible={false} on the outer container; inner elements handle their own accessibility.accessibilityHint="" on the FAB and LoadLatestBtn is a known gap.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Individual post card rendering author information, rich text content, embedded media, and interaction controls (like, repost, reply).
When to Use:
renderItem output for post feeds, search results, and topic feeds.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
post |
AppBskyFeedDefs.PostView |
Yes | — | The post data to render |
record |
AppBskyFeedPost.Record |
Yes | — | The post record content |
moderation |
ModerationDecision |
Yes | — | Moderation decision for this post |
onBeforePress |
() => void |
No | — | Callback fired before navigating to the post thread |
showReplyTo |
boolean |
No | false |
Whether to show the reply-to indicator |
isThreadChild |
boolean |
No | false |
Whether this post is a child in a thread |
isThreadParent |
boolean |
No | false |
Whether this post is a parent in a thread |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Normal post | Full post card with author, content, controls |
| Moderated | Moderation decision | Content may be blurred or hidden |
| Thread child | isThreadChild={true} |
Indented with connecting line |
Composition:
<List
data={posts}
renderItem={({ item }) => (
<Post
post={item.post}
record={item.record}
moderation={item.moderation}
onBeforePress={onBeforePress}
/>
)}
keyExtractor={item => item.post.uri}
/>
Accessibility:
accessible={false} on the outer Link — inner elements handle their own accessibility.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Feed generator card displaying the feed's name, description, avatar, and a subscribe/save action.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
view |
AppBskyFeedDefs.GeneratorView |
Yes | — | The feed generator data |
showSaveBtn |
boolean |
No | false |
Whether to show a save/pin button |
showDescription |
boolean |
No | true |
Whether to show the feed description |
showLikes |
boolean |
No | false |
Whether to show the like count |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Feed avatar, name, description |
| With save button | showSaveBtn={true} |
Save/pin button visible |
Composition:
<FeedCard.Default
view={feedGeneratorView}
showSaveBtn
showDescription
/>
Accessibility:
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Full profile card with avatar, display name, handle, follow button, and bio description, used in explore and suggestion surfaces.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewBasic |
Yes | — | The profile data |
moderationOpts |
ModerationOpts |
Yes | — | Moderation options for content filtering |
logContext |
string |
No | — | Analytics context string |
recId |
string |
No | — | Recommendation ID for analytics |
sourceContext |
object |
No | — | Source context for analytics attribution |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Avatar, name, handle, follow button, description |
| Moderated | Moderation decision | Content may be blurred |
Composition:
<ProfileCard.Default
profile={actor}
moderationOpts={moderationOpts}
logContext="Explore"
recId={actor.recId}
/>
Accessibility:
ProfileCard.Link wraps the card in a navigable link element.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: User avatar sub-component within a profile card, with moderation applied.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewBasic |
Yes | — | Profile data |
moderationOpts |
ModerationOpts |
Yes | — | Moderation options |
size |
number |
No | 42 |
Avatar size in logical pixels |
Composition:
<ProfileCard.Outer>
<ProfileCard.Header>
<ProfileCard.Avatar profile={profile} moderationOpts={moderationOpts} />
<ProfileCard.NameAndHandle profile={profile} />
</ProfileCard.Header>
</ProfileCard.Outer>
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Display name and @handle sub-component within a profile card.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewBasic |
Yes | — | Profile data |
moderationOpts |
ModerationOpts |
Yes | — | Moderation options |
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Follow/unfollow button sub-component within a profile card, with analytics tracking.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewBasic |
Yes | — | Profile data |
logContext |
string |
No | — | Analytics context |
withIcon |
boolean |
No | true |
Whether to show an icon inside the button |
onFollow |
() => void |
No | — | Callback fired after following |
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Bio/description text sub-component within a profile card, clamped to 2 lines.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewBasic |
Yes | — | Profile data |
numberOfLines |
number |
No | 2 |
Maximum number of lines to display |
Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Navigation link wrapper that makes an entire profile card tappable, navigating to the profile screen.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewBasic |
Yes | — | Profile data used to construct the navigation target |
label |
string |
No | — | Accessible label for the link |
children |
React.ReactNode |
Yes | — | Card content |
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Starter pack card displaying an avatar stack of members, the pack name, description, and a "Follow all" button with analytics tracking.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
view |
AppBskyGraphDefs.StarterPackView |
Yes | — | The starter pack data |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Avatar stack, name, description, "Follow all" button |
| Processing | Follow-all in progress | Loader icon in button, button disabled |
| All followed | Follow-all succeeded | Checkmark icon in button, button disabled |
Composition:
<StarterPackCard view={starterPackView} />
Accessibility:
label prop.Known Issues:
return after bulkWriteFollows catch block may cause a secondary runtime error on follow-all failure.IGNORED_ACCOUNT DID is hardcoded with no documentation.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Conversation row displaying the other user's avatar, display name, last message preview, elapsed time, unread indicator, and swipe gesture actions.
When to Use:
renderItem output for the Chat List and Inbox screens.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
convo |
ChatBskyConvoDefs.ConvoView |
Yes | — | The conversation data |
onPress |
() => void |
No | — | Custom press handler |
showMenu |
boolean |
No | false |
Whether to show the ConvoMenu |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Long press | Opens ConvoMenu |
Opens ConvoMenu |
Plays haptic on native |
| Swipe gestures | GestureActionView |
GestureActionView |
Mark as read / delete |
| Hover/focus | Shows ConvoMenu trigger |
— | Web only |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Normal conversation | Avatar, name, last message, time |
| Unread | convo.unreadCount > 0 |
Bold last message, blue unread dot |
| Deleted account | handle === 'missing.invalid' |
"Deleted Account" name, muted styling |
| Muted/blocked | convo.muted or blocked |
Dimmed styling, muted bell icon |
Composition:
<List
data={conversations}
renderItem={({ item }) => <ChatListItem convo={item} />}
keyExtractor={item => item.id}
/>
Accessibility:
accessibilityActions includes magicTap and longpress on native.Known Issues:
ChatListItemPortal pattern for absolutely positioned action buttons is fragile; the spacer height must match the button height.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Profile card with a checkbox interaction model for multi-select in the Starter Pack wizard's profile selection step.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewBasic |
Yes | — | Profile data |
moderationOpts |
ModerationOpts |
Yes | — | Moderation options |
state |
WizardState |
Yes | — | Wizard state for determining checked status |
dispatch |
WizardDispatch |
Yes | — | Wizard dispatch for toggling selection |
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Feed card with a checkbox interaction model for multi-select in the Starter Pack wizard's feed selection step.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
feed |
AppBskyFeedDefs.GeneratorView |
Yes | — | Feed generator data |
state |
WizardState |
Yes | — | Wizard state for determining checked status |
dispatch |
WizardDispatch |
Yes | — | Wizard dispatch for toggling selection |
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Renders AT Protocol rich text with inline facets (links, mentions, hashtags) as interactive inline elements.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
value |
RichTextAPI |
Yes | — | Pre-parsed AT Protocol rich text object |
numberOfLines |
number |
No | — | Maximum number of lines to display |
style |
StyleProp<TextStyle> |
No | — | Additional text styles |
enableTags |
boolean |
No | false |
Whether to render hashtag facets as links |
authorHandle |
string |
No | — | Author handle for tag link construction |
onLinkPress |
(url: string) => void |
No | — | Custom handler for link presses |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Plain text | Unstyled text |
| With links | Facets present | Inline tappable links in primary color |
| Truncated | numberOfLines set |
Text truncated with ellipsis |
Composition:
<RichText
value={descriptionRT}
numberOfLines={15}
enableTags
authorHandle={profile.handle}
/>
Accessibility:
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Themed text component with emoji support, used throughout the application for all text rendering.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
style |
StyleProp<TextStyle> |
No | — | Additional text styles |
emoji |
boolean |
No | false |
Enables emoji rendering support |
numberOfLines |
number |
No | — | Maximum number of lines |
selectable |
boolean |
No | false |
Whether text is selectable |
children |
React.ReactNode |
Yes | — | Text content |
Composition:
<Text style={[a.text_xl, a.font_bold, t.atoms.text]}>
Display Name
</Text>
Accessibility:
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Centered icon and message component displayed when a list or feed has no content to show.
When to Use:
renderEmptyState callback output for PostFeed or List.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
icon |
React.ReactNode |
No | — | Icon to display above the message |
message |
string |
Yes | — | Human-readable empty state message |
button |
EmptyStateButtonProps |
No | — | Optional call-to-action button |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | No items | Icon + message centered |
| With button | button prop provided |
Icon + message + CTA button |
Composition:
<PostFeed
feed={feed}
renderEmptyState={() => (
<EmptyState
icon={<EditIcon size="3xl" />}
message="No posts yet"
/>
)}
/>
Accessibility:
Related Components:
Category: Feedback
Library: Custom
Platform: iOS + Android
Summary: Styled callout box for displaying informational, tip, warning, or error messages to the user.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
type |
'info' | 'tip' | 'warning' | 'error' |
Yes | — | Visual style of the callout |
children |
React.ReactNode |
Yes | — | Callout content |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Info | type="info" |
Blue-tinted informational box |
| Tip | type="tip" |
Neutral tip box |
| Warning | type="warning" |
Yellow-tinted warning box |
| Error | type="error" |
Red-tinted error box |
Composition:
{isError && (
<Admonition type="error">
<Trans>Failed to load notification settings.</Trans>
</Admonition>
)}
Accessibility:
type="error" variant should ideally use role="alert" or aria-live="assertive" — this is not confirmed from the source and may be a gap.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Character count progress indicator displayed as {n}/50 overlaid on a text field, showing how many characters have been used.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
count |
number |
Yes | — | Current character count |
max |
number |
Yes | — | Maximum allowed character count |
Composition:
<View style={a.relative}>
<TextField.Root>
<TextField.Input
value={name}
onChangeText={text => dispatch({ type: 'SetName', name: text })}
/>
</TextField.Root>
<CharProgress count={state.name?.length ?? 0} max={50} />
</View>
Accessibility:
TextField.SuffixText variant used in Step Details has an explicit label prop for screen readers.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Horizontal stack of overlapping circular profile avatars, used to preview a group of users.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profiles |
AppBskyActorDefs.ProfileViewBasic[] |
Yes | — | Array of profiles to display |
numPending |
number |
No | — | Number of profiles to show |
total |
number |
No | — | Total count for overflow display |
size |
number |
No | 20 |
Avatar size in logical pixels |
Composition:
<AvatarStack
profiles={inboxUnreadConvoMembers.slice(0, 3)}
numPending={3}
total={inboxUnreadConvoMembers.length}
/>
Accessibility:
UserAvatar implementation.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Inline verification or role badges rendered next to a user's display name.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewDetailed |
Yes | — | Profile data |
size |
'sm' | 'md' | 'lg' | 'xl' |
No | 'md' |
Badge size |
interactive |
boolean |
No | false |
Whether badges are tappable |
Composition:
<View style={a.flex_row}>
<Text style={a.font_bold}>{displayName}</Text>
<ProfileBadges profile={profile} size="xl" interactive />
</View>
Accessibility:
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Displays mutual follower avatars and a count, providing social context when viewing a profile or chat request.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewDetailed |
Yes | — | Profile data containing knownFollowers |
moderationOpts |
ModerationOpts |
Yes | — | Moderation options |
Composition:
<KnownFollowers
profile={otherUser}
moderationOpts={moderationOpts}
/>
Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Floating circular button that appears when the user has scrolled down or new posts are available, allowing one-tap return to the top of a feed.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onPress |
() => void |
Yes | — | Callback to scroll to top and load new posts |
label |
string |
Yes | — | Accessible label for the button |
showIndicator |
boolean |
No | false |
Whether to show a dot indicating new content |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | isScrolledDown or hasNew |
Circular button visible |
| With indicator | showIndicator={true} |
Blue dot on the button |
Composition:
{(isScrolledDown || hasNew) && (
<LoadLatestBtn
onPress={onScrollToTop}
label={_(msg`Load new posts`)}
showIndicator={hasNew}
/>
)}
Accessibility:
label prop provides the accessible name.accessibilityHint="" is a known gap — the hint is empty.Related Components:
Category: Feedback
Library: Custom
Platform: iOS + Android
Summary: Animated loading spinner icon, used both as a standalone centered spinner and as an icon inside buttons during async operations.
When to Use:
Button via ButtonIcon to indicate a pending async operation.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
size |
'sm' | 'md' | 'lg' | 'xl' |
No | 'md' |
Spinner size |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Rendered | Animated spinning indicator |
Composition:
{/* As a section loading indicator */}
{isPending && <Loader size="xl" />}
{/* Inside a button */}
<Button label="Save" onPress={onSave} disabled={isPending}>
{isPending && <ButtonIcon icon={Loader} />}
<ButtonText>Save</ButtonText>
</Button>
Accessibility:
accessibilityLabel is set in most usages — screen readers may not announce the loading state. This is a known gap.Related Components:
LoadingPlaceholder)Category: Feedback
Library: Custom
Platform: iOS + Android
Summary: Shimmer placeholder component that mimics the shape of content while it is loading, reducing perceived load time.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
width |
number | string |
No | — | Width of the placeholder |
height |
number |
No | — | Height of the placeholder |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
{isLoading ? (
<View style={a.flex_row}>
<Skeleton width={40} height={40} style={a.rounded_full} />
<Skeleton width={120} height={16} style={a.ml_sm} />
</View>
) : (
<ProfileCard.Default profile={profile} />
)}
Accessibility:
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Styled container for a text input field that provides border, background, and validation state styling.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isInvalid |
boolean |
No | false |
Applies invalid/error styling to the field border |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
children |
React.ReactNode |
Yes | — | TextField.Icon, TextField.Input, TextField.SuffixText |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Neutral border and background |
| Invalid | isInvalid={true} |
Red/error-colored border |
Composition:
<TextField.LabelText>Email</TextField.LabelText>
<TextField.Root isInvalid={errorField === 'identifier'}>
<TextField.Icon icon={At} />
<TextField.Input
label="Enter your email"
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={setEmail}
/>
</TextField.Root>
Accessibility:
isInvalid should propagate aria-invalid to the underlying input — depends on component implementation.Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: The actual text input element within a TextField.Root, with support for all React Native TextInput props plus an accessible label.
When to Use:
TextField.Root for any text entry field.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label |
string |
Yes | — | Accessible label for the input (used as accessibilityLabel) |
value |
string |
No | — | Controlled input value |
onChangeText |
(text: string) => void |
No | — | Callback fired on text change |
keyboardType |
'default' | 'email-address' | 'phone-pad' | 'numeric' | 'decimal-pad' |
No | 'default' |
Keyboard type |
returnKeyType |
'next' | 'done' | 'go' | 'search' |
No | — | Return key label |
onSubmitEditing |
() => void |
No | — | Callback when the return key is pressed |
secureTextEntry |
boolean |
No | false |
Hides input for passwords |
autoCapitalize |
'none' | 'sentences' | 'words' | 'characters' |
No | 'sentences' |
Auto-capitalization behavior |
autoCorrect |
boolean |
No | true |
Whether autocorrect is enabled |
autoComplete |
string |
No | — | Autofill hint |
autoFocus |
boolean |
No | false |
Whether to focus the input on mount |
multiline |
boolean |
No | false |
Whether the input supports multiple lines |
maxLength |
number |
No | — | Maximum character length |
editable |
boolean |
No | true |
Whether the input is editable |
accessibilityHint |
string |
No | — | Additional context for screen readers |
blurOnSubmit |
boolean |
No | true |
Whether to blur on submit |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
returnKeyType |
Supported | Supported | Controls the keyboard's return key label |
enablesReturnKeyAutomatically |
Supported | Not supported | iOS-only prop to disable return key when empty |
Composition:
<TextField.Root>
<TextField.Input
label="Password"
secureTextEntry
returnKeyType="done"
onSubmitEditing={onPressNext}
accessibilityHint="Enter your account password"
/>
</TextField.Root>
Accessibility:
label prop maps to accessibilityLabel.accessibilityHint provides additional context.Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Visible label rendered above a text field, providing both visual and programmatic association between the label and the input.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | Label text content |
Composition:
<TextField.LabelText>
<Trans>Email address</Trans>
</TextField.LabelText>
<TextField.Root>
<TextField.Input label="Email address" keyboardType="email-address" />
</TextField.Root>
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Leading icon rendered inside a TextField.Root, providing a visual category indicator for the input.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
icon |
React.ComponentType |
Yes | — | Icon component to render |
Composition:
<TextField.Root>
<TextField.Icon icon={Lock} />
<TextField.Input label="Password" secureTextEntry />
</TextField.Root>
Accessibility:
TextField.Input's label provides the accessible name.Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Right-aligned suffix text rendered inside a TextField.Root, used for character counters or unit labels.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label |
string |
No | — | Accessible label for the suffix |
children |
React.ReactNode |
Yes | — | Suffix content |
Composition:
<TextField.Root>
<TextField.Input label="Pack name" value={name} onChangeText={setName} />
<TextField.SuffixText label={`${name.length} out of 50`}>
{name.length}/50
</TextField.SuffixText>
</TextField.Root>
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Container managing a group of checkbox or radio toggle items, enforcing single-selection (radio) or multi-selection (checkbox) behavior.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
type |
'checkbox' | 'radio' |
Yes | — | Selection mode |
values |
string[] |
Yes | — | Currently selected values |
onChange |
(values: string[]) => void |
Yes | — | Callback when selection changes |
label |
string |
Yes | — | Accessible label for the group |
disabled |
boolean |
No | false |
Whether the group is non-interactive |
children |
React.ReactNode |
Yes | — | Toggle.Item children |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Checkbox | type="checkbox" |
Multiple items can be selected |
| Radio | type="radio" |
Only one item can be selected |
| Disabled | disabled={true} |
All items are non-interactive |
Composition:
<Toggle.Group
type="checkbox"
values={channels}
onChange={onChangeChannels}
label={_(msg`Select your preferred notification channels`)}
>
<Toggle.Item name="push" label={_(msg`Receive push notifications`)}>
<Toggle.LabelText>Push notifications</Toggle.LabelText>
<Toggle.Platform />
</Toggle.Item>
<Toggle.Item name="list" label={_(msg`Receive in-app notifications`)}>
<Toggle.LabelText>In-app notifications</Toggle.LabelText>
<Toggle.Platform />
</Toggle.Item>
</Toggle.Group>
Accessibility:
label prop provides the accessible group label.type="checkbox" maps to ARIA checkbox group semantics; type="radio" maps to radio group semantics.Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Individual selectable item within a Toggle.Group, providing interaction state context to its children.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
Yes | — | The value this item represents in the group |
label |
string |
Yes | — | Accessible label for this item |
type |
'checkbox' | 'radio' |
No | — | Overrides the group type for this item |
disabled |
boolean |
No | false |
Whether this item is non-interactive |
children |
React.ReactNode |
Yes | — | Toggle.LabelText, Toggle.Platform, or Toggle.Radio |
Composition:
<Toggle.Item name="push" label={_(msg`Receive push notifications`)}>
<Toggle.LabelText>Push notifications</Toggle.LabelText>
<Toggle.Platform />
</Toggle.Item>
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Platform-appropriate toggle control that renders as a native Switch on iOS/Android and a checkbox or toggle on web.
When to Use:
Toggle.Item when a platform-native toggle switch is desired.Composition:
<Toggle.Item name="haptics" label={_(msg`Disable haptic feedback`)}>
<Toggle.LabelText>Disable haptic feedback</Toggle.LabelText>
<Toggle.Platform />
</Toggle.Item>
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Appearance | Native Switch |
Native Switch |
Web renders a checkbox or custom toggle |
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Radio button indicator rendered within a Toggle.Item in a radio-type Toggle.Group.
Composition:
<Toggle.Group type="radio" values={[sort]} onChange={setSort} label="Sort replies by">
<Toggle.Item name="top" label={_(msg`Top replies first`)}>
<Toggle.Radio />
<Toggle.LabelText>Top replies first</Toggle.LabelText>
</Toggle.Item>
</Toggle.Group>
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Styled label text rendered within a Toggle.Item, associated with the toggle control.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | Label text content |
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Dropdown/select control for single-value selection from a list of options.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
value |
string |
Yes | — | Currently selected value |
onChange |
(value: string) => void |
Yes | — | Callback when selection changes |
label |
string |
Yes | — | Accessible label for the select |
children |
React.ReactNode |
Yes | — | Select.Trigger and Select.Content |
Composition:
<Select.Root
value={langPrefs.appLanguage}
onChange={onChangeAppLanguage}
label={_(msg`Select app language`)}
>
<Select.Trigger>
<Select.ValueText />
</Select.Trigger>
<Select.Content>
{APP_LANGUAGES.map(lang => (
<Select.Item key={lang.code2} value={lang.code2} label={lang.name} />
))}
</Select.Content>
</Select.Root>
Accessibility:
label prop provides the accessible name for the select control.Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Search text input with a clear button, optional hotkey support, and focus management for the search shell.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
value |
string |
Yes | — | Controlled input value |
onChangeText |
(text: string) => void |
Yes | — | Callback on text change |
onClearText |
() => void |
No | — | Callback when the clear button is pressed |
onFocus |
() => void |
No | — | Callback when the input gains focus |
onSubmitEditing |
() => void |
No | — | Callback when the return key is pressed |
placeholder |
string |
No | — | Placeholder text |
hotkey |
boolean |
No | false |
Whether a keyboard shortcut focuses this input |
ref |
React.RefObject<TextInput> |
No | — | Ref for programmatic focus control |
Composition:
<SearchInput
ref={textInput}
value={searchText}
onChangeText={onChangeText}
onClearText={onPressClearQuery}
onFocus={onSearchInputFocus}
onSubmitEditing={onSubmit}
placeholder="Search..."
hotkey
/>
Accessibility:
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Radio-style segmented button group where the user selects one option from a horizontally arranged set.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
values |
string[] |
Yes | — | Currently selected values (typically one item for radio behavior) |
onChange |
(values: string[]) => void |
Yes | — | Callback when selection changes |
type |
'radio' | 'checkbox' |
No | 'radio' |
Selection mode |
label |
string |
Yes | — | Accessible label for the group |
children |
React.ReactNode |
Yes | — | SegmentedControl.Item children |
Composition:
<SegmentedControl.Root
values={[colorMode]}
onChange={values => setColorMode(values[0])}
type="radio"
label={_(msg`Color mode`)}
>
<SegmentedControl.Item value="system" label={_(msg`System`)}>
<SegmentedControl.ItemText>System</SegmentedControl.ItemText>
</SegmentedControl.Item>
<SegmentedControl.Item value="light" label={_(msg`Light`)}>
<SegmentedControl.ItemText>Light</SegmentedControl.ItemText>
</SegmentedControl.Item>
<SegmentedControl.Item value="dark" label={_(msg`Dark`)}>
<SegmentedControl.ItemText>Dark</SegmentedControl.ItemText>
</SegmentedControl.Item>
</SegmentedControl.Root>
Related Components:
Category: Form
Library: Custom
Platform: iOS + Android
Summary: Individual segment within a SegmentedControl.Root.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
value |
string |
Yes | — | The value this segment represents |
label |
string |
Yes | — | Accessible label for this segment |
children |
React.ReactNode |
Yes | — | Segment content (typically SegmentedControl.ItemText) |
Related Components:
Category: Feedback
Library: Custom
Platform: iOS + Android
Summary: Transient notification message displayed at the bottom of the screen to confirm actions or report errors, invoked imperatively via Toast.show().
When to Use:
When NOT to Use:
Admonition or FormError instead.Prompt.Basic instead.Props (via Toast.show()):
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
message |
string |
Yes | — | The message to display |
type |
'default' | 'error' |
No | 'default' |
Visual style of the toast |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Toast.show(message) |
Neutral toast at bottom of screen |
| Error | Toast.show(message, { type: 'error' }) |
Red-tinted error toast |
Composition:
// Imperative usage — not rendered in JSX
Toast.show('Profile updated')
Toast.show('Failed to save changes', { type: 'error' })
Accessibility:
aria-live region — implementation depends on the Toast component internals.Known Issues:
Toast.show in src/view/com/util/Toast.tsx is marked @deprecated — should use Toast from #/components/Toast.Related Components:
Category: Feedback
Library: Custom
Platform: iOS + Android
Summary: Simple confirmation dialog with a title, description, and confirm/cancel buttons, used for destructive action confirmations.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
control |
PromptControlProps |
Yes | — | Control object from Prompt.usePromptControl() |
title |
string |
Yes | — | Dialog title |
description |
string |
Yes | — | Dialog description |
onConfirm |
() => void |
Yes | — | Callback when the confirm button is pressed |
confirmButtonCta |
string |
No | "Confirm" |
Label for the confirm button |
isDestructive |
boolean |
No | false |
Whether the confirm button uses destructive styling |
cancelButtonCta |
string |
No | "Cancel" |
Label for the cancel button |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | control.open() |
Modal dialog with title, description, buttons |
| Destructive | isDestructive={true} |
Confirm button in red/negative color |
Composition:
const deleteControl = Prompt.usePromptControl()
<Prompt.Basic
control={deleteControl}
title="Delete account"
description="This action cannot be undone."
onConfirm={onDeleteAccount}
confirmButtonCta="Delete"
isDestructive
/>
// Trigger:
<Button onPress={deleteControl.open}>
<ButtonText>Delete account</ButtonText>
</Button>
Accessibility:
Related Components:
Category: Feedback
Library: Custom
Platform: iOS + Android
Summary: Outer wrapper for a prompt dialog, used with Prompt.Basic when the prompt needs to be rendered outside the normal component tree (e.g., at the screen root).
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
control |
PromptControlProps |
Yes | — | Control object from Prompt.usePromptControl() |
children |
React.ReactNode |
Yes | — | Prompt.Basic content |
Related Components:
Category: Feedback
Library: Custom
Platform: iOS + Android
Summary: Full-page error display with an exclamation icon, title, message, optional technical details, and an optional retry button.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
title |
string |
Yes | — | Bold error heading |
message |
string |
Yes | — | Descriptive error message |
details |
string |
No | — | Technical error details shown in a bordered box |
onPressTryAgain |
() => void |
No | — | Retry callback; renders a retry button when provided |
showHeader |
boolean |
No | false |
Whether to render a Layout.Header.Outer with a back button |
testID |
string |
No | — | Test identifier |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Rendered | Icon, title, message |
| With details | details provided |
Technical details box below message |
| With retry | onPressTryAgain provided |
"Try again" button below details |
| With header | showHeader={true} |
Navigation header with back button |
Composition:
{isError && (
<ErrorScreen
title="Oops!"
message="We couldn't load that page."
details={cleanError(error)}
onPressTryAgain={refetch}
/>
)}
Accessibility:
accessibilityHint="Retries the last action, which errored out".label={_(msgRetry)}.Related Components:
Category: Feedback
Library: Custom
Platform: iOS + Android
Summary: Inline error message displayed within a form, typically below the form fields and above the submit button.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
message |
string |
Yes | — | The error message to display |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | message is non-empty |
Red error text |
| Hidden | message is empty |
Not rendered |
Composition:
<FormError message={error} />
Accessibility:
Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Horizontal tab label row rendered above a Pager, allowing users to tap to switch between tabs.
When to Use:
renderTabBar prop of a Pager component.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items |
string[] |
Yes | — | Array of tab label strings |
selectedPage |
number |
Yes | — | Index of the currently selected tab |
onSelect |
(index: number) => void |
Yes | — | Callback when a tab is tapped |
testID |
string |
No | — | Test identifier |
Composition:
<Pager
renderTabBar={props => (
<Layout.Center>
<TabBar
items={['Top', 'Latest', 'People', 'Feeds']}
selectedPage={activeTab}
onSelect={onPageSelected}
{...props}
/>
</Layout.Center>
)}
>
{/* tab content */}
</Pager>
Accessibility:
role="tab" semantics — depends on component implementation.Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Swipeable horizontal tab container that manages multiple tab views and exposes onPageSelected for tab change events.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
renderTabBar |
(props: PagerTabBarProps) => JSX.Element |
Yes | — | Render prop for the tab bar |
onPageSelected |
(index: number) => void |
No | — | Callback when the active page changes |
initialPage |
number |
No | 0 |
Initial active page index |
children |
React.ReactNode |
Yes | — | Tab content panels |
Composition:
<Pager
renderTabBar={props => <TabBar items={['Top', 'Latest']} {...props} />}
onPageSelected={setActiveTab}
initialPage={0}
>
<View>{/* Top tab content */}</View>
<View>{/* Latest tab content */}</View>
</Pager>
Accessibility:
Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Tabbed pager with a sticky header and synchronized scroll behavior, used for profile screens and the Starter Pack detail screen.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
items |
string[] |
Yes | — | Tab label strings |
renderHeader |
() => JSX.Element |
Yes | — | Render prop for the sticky header content |
onPageSelected |
(index: number) => void |
No | — | Callback when the active page changes |
initialPage |
number |
No | 0 |
Initial active page index |
children |
React.ReactNode |
Yes | — | Tab content panels |
Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Three-dot overflow menu for profile actions including report, mute, block, share, and list management.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewDetailed |
Yes | — | Profile data |
moderation |
ModerationDecision |
Yes | — | Moderation decision |
blockInfo |
BlockInfo |
No | — | Block relationship information |
Composition:
<ProfileMenu
profile={profile}
moderation={moderation}
blockInfo={blockInfo}
/>
Accessibility:
Related Components:
ConvoMenu)Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Context menu for conversation actions including mute, block, report, leave, and mark as read.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
convo |
ChatBskyConvoDefs.ConvoView |
Yes | — | Conversation data |
profile |
AppBskyActorDefs.ProfileViewBasic |
Yes | — | The other user's profile |
blockInfo |
BlockInfo |
No | — | Block relationship information |
latestReportableMessage |
MessageView |
No | — | Most recent reportable message |
Composition:
<ConvoMenu
convo={convo}
profile={otherUser}
blockInfo={blockInfo}
latestReportableMessage={latestReportableMessage}
/>
Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Tappable settings row that navigates to another screen when pressed, with an optional badge and a trailing chevron icon.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
to |
string | object |
Yes | — | Navigation destination (route name or path) |
label |
string |
Yes | — | Accessible label for the item |
accessibilityHint |
string |
No | — | Additional accessibility context |
children |
React.ReactNode |
Yes | — | Row content (icon, text, badge) |
Composition:
<SettingsList.LinkItem
to="/settings/app-passwords"
label={_(msg`App passwords`)}
>
<SettingsList.ItemIcon icon={KeyIcon} />
<SettingsList.ItemText>App passwords</SettingsList.ItemText>
{appPasswords?.length > 0 && (
<SettingsList.BadgeText>{appPasswords.length}</SettingsList.BadgeText>
)}
</SettingsList.LinkItem>
Accessibility:
label prop provides the accessible name.Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Tappable settings row that triggers an action when pressed (not navigation), with hover/press visual feedback.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onPress |
() => void |
Yes | — | Action callback |
label |
string |
Yes | — | Accessible label for the item |
destructive |
boolean |
No | false |
Whether the item uses destructive styling |
disabled |
boolean |
No | false |
Whether the item is non-interactive |
children |
React.ReactNode |
Yes | — | Row content |
Composition:
<SettingsList.PressableItem
onPress={signOutControl.open}
label={_(msg`Sign out`)}
destructive
>
<SettingsList.ItemIcon icon={SignOutIcon} />
<SettingsList.ItemText>Sign out</SettingsList.ItemText>
</SettingsList.PressableItem>
Related Components:
Category: Gesture & Touch
Library: Custom (react-native-gesture-handler, react-native-reanimated)
Platform: iOS + Android
Summary: Horizontal swipe gesture wrapper that reveals action buttons (mark as read, delete) at configurable pixel thresholds, with haptic feedback and animated background color transitions.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
actions |
GestureActions |
Yes | — | Object defining leftFirst, leftSecond, rightFirst, rightSecond action configurations |
children |
React.ReactNode |
Yes | — | The list item content that slides |
GestureAction shape:
| Field | Type | Description |
|---|---|---|
threshold |
number |
Pixel distance to trigger this action |
color |
string |
Background color shown when this action is active |
icon |
React.ComponentType |
Icon rendered in the background |
action |
() => void |
Callback fired when the gesture ends at this threshold |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Haptic feedback | Fired on threshold crossing | Fired on threshold crossing | Via useHaptics() |
| Reduced motion | Icon scale animation suppressed | Icon scale animation suppressed | Respects useReducedMotion() |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | No swipe | Content at rest position |
| First threshold | Swipe past leftFirst.threshold |
Background color + icon appear |
| Second threshold | Swipe past leftSecond.threshold |
Background color changes to second action color |
Composition:
<GestureActionView
actions={{
rightFirst: {
threshold: 120,
color: t.palette.primary_500,
icon: CheckIcon,
action: () => markAsRead({ convoId: convo.id }),
},
rightSecond: {
threshold: 225,
color: t.palette.negative_500,
icon: TrashIcon,
action: () => leaveConvoPrompt.open(),
},
}}
>
<ChatListItem convo={convo} />
</GestureActionView>
Accessibility:
Known Issues:
onEnd right-side first action: condition uses hitSecond.get() instead of hitFirst.get(), making rightFirst.action unreachable when rightSecond is also configured.MAX_WIDTH is computed once at module load and does not respond to orientation changes.Related Components:
Category: Gesture & Touch
Library: Custom (useDraggableScroll hook)
Platform: Web
Summary: Horizontal ScrollView with click-and-drag scrolling for desktop/web users, with user-select: none applied to prevent text selection during drag.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
ref |
React.RefObject<ScrollView> |
No | — | Forwarded ref for programmatic scroll control |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
children |
React.ReactNode |
Yes | — | Horizontally scrollable content |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Drag scroll | Not applicable | Not applicable | Web-only feature |
user-select: none |
Not applied | Not applied | Applied only on web via web(a.user_select_none) |
Composition:
<DraggableScrollView horizontal showsHorizontalScrollIndicator={false}>
{videoCards.map(card => <CompactVideoPostCard key={card.uri} post={card} />)}
</DraggableScrollView>
Known Issues:
cursor: 'grab' is hardcoded and not exposed as a prop.horizontal is hardcoded; cannot be reused for vertical drag-scroll.Related Components:
Category: Gesture & Touch
Library: Custom (react-native-reanimated)
Platform: iOS + Android
Summary: Pressable with Reanimated-driven opacity or scale animation on press, used for interactive overlay elements.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onPress |
() => void |
No | — | Press callback |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
children |
React.ReactNode |
Yes | — | Content |
Related Components:
Category: Gesture & Touch
Library: Custom
Platform: iOS + Android
Summary: Low-opacity hover/press highlight overlay that provides visual feedback without a heavy highlight effect.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
hover |
boolean |
Yes | — | Whether the hover/press state is active |
children |
React.ReactNode |
Yes | — | Content |
Composition:
<Link to={trend.link}>
{({ hovered, pressed }) => (
<>
<SubtleHover hover={hovered || pressed} />
<TrendingTopic trend={trend} />
</>
)}
</Link>
Related Components:
Category: Gesture & Touch
Library: Custom (react-native-gesture-handler)
Platform: iOS + Android
Summary: Wrapper component that prevents the navigation drawer from intercepting horizontal swipe gestures within its children, enabling horizontal scroll within a drawer-enabled layout.
When to Use:
ScrollView or DraggableScrollView components inside screens that have a navigation drawer.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | Content that should receive horizontal swipe gestures |
Composition:
<BlockDrawerGesture>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{/* horizontal content */}
</ScrollView>
</BlockDrawerGesture>
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Root dialog container that presents a modal overlay with native or web presentation options.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
control |
DialogControlProps |
Yes | — | Control object from useDialogControl() |
nativeOptions |
object |
No | — | Native-specific presentation options (e.g., { sheet: { snapPoints: [...] } }) |
webOptions |
object |
No | — | Web-specific options (e.g., { onBackgroundPress }) |
children |
React.ReactNode |
Yes | — | Dialog content |
Composition:
const control = useDialogControl()
<Dialog.Outer control={control}>
<Dialog.ScrollableInner label={_(msg`Edit profile`)}>
{/* dialog content */}
</Dialog.ScrollableInner>
</Dialog.Outer>
// Trigger:
<Button onPress={control.open}>
<ButtonText>Edit Profile</ButtonText>
</Button>
Accessibility:
Dialog.ScrollableInner has a label prop for screen reader announcement.Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Scrollable content area within a dialog, with a header slot for title and action buttons.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label |
string |
Yes | — | Accessible label for the dialog |
children |
React.ReactNode |
Yes | — | Dialog body content |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
<Dialog.Outer control={control}>
<Dialog.ScrollableInner label={_(msg`Change Handle`)}>
<Dialog.Header>
<Dialog.HeaderText>Change Handle</Dialog.HeaderText>
</Dialog.Header>
{/* form content */}
</Dialog.ScrollableInner>
</Dialog.Outer>
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Styled text input for use inside a dialog, with the same props as TextField.Input but styled for the dialog context.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label |
string |
Yes | — | Accessible label |
value |
string |
No | — | Controlled value |
onChangeText |
(text: string) => void |
No | — | Change callback |
testID |
string |
No | — | Test identifier |
autoFocus |
boolean |
No | false |
Whether to focus on mount |
Composition:
<Dialog.Input
label={_(msg`Confirmation code`)}
value={confirmCode}
onChangeText={setConfirmCode}
testID="confirmationCode"
autoFocus
/>
Related Components:
createPortalGroupCategory: Utility
Library: Custom
Platform: iOS + Android
Summary: React context-based teleportation mechanism that renders child content in a different position in the component tree, used in SettingsList.Group and the Onboarding Layout.
When to Use:
Props (via createPortalGroup()):
Returns { Provider, Portal, Outlet }:
Provider — wraps the component tree that contains both Portal and OutletPortal — declares content to be teleportedOutlet — renders the teleported content at the desired locationComposition:
// In SettingsList.Group:
const { Provider, Portal, Outlet } = createPortalGroup()
<Provider>
<View style={styles.headerRow}>
<Outlet /> {/* ItemIcon and ItemText render here */}
</View>
<View style={styles.contentRow}>
{children} {/* Toggle items render here */}
</View>
</Provider>
// Usage:
<SettingsList.Group>
<SettingsList.ItemIcon icon={BellIcon} /> {/* Teleported to header row */}
<SettingsList.ItemText>Notifications</SettingsList.ItemText> {/* Teleported */}
<Toggle.Item name="push">...</Toggle.Item> {/* Stays in content row */}
</SettingsList.Group>
Related Components:
Category: Utility
Library: Custom (react-native-reanimated)
Platform: iOS + Android
Summary: Directional slide/fade animation wrapper for wizard step transitions, driven by a direction prop.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
direction |
'Forward' | 'Backward' |
Yes | — | Animation direction |
enabledWeb |
boolean |
No | true |
Whether to apply the animation on web |
children |
React.ReactNode |
Yes | — | Step content |
Composition:
<ScreenTransition direction={state.transitionDirection}>
<StepDetails state={state} dispatch={dispatch} />
</ScreenTransition>
Related Components:
Category: Utility
Library: expo-bluesky-gif-view (Custom Expo Module)
Platform: iOS + Android
Summary: Native GIF playback component with imperative playAsync(), pauseAsync(), and toggleAsync() methods, and a prefetchAsync() static method for pre-loading.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
source |
string |
No | — | URL of the GIF to play |
autoplay |
boolean |
No | false |
Whether to begin playback immediately on load |
placeholderSource |
string |
No | — | URL of a placeholder image shown while loading |
onPlayerStateChange |
(event: GifViewStateChangeEvent) => void |
No | — | Callback fired when isPlaying or isLoaded changes |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Imperative Methods (via ref):
| Method | Returns | Description |
|---|---|---|
playAsync() |
Promise<void> |
Starts or resumes GIF playback |
pauseAsync() |
Promise<void> |
Pauses GIF playback |
toggleAsync() |
Promise<void> |
Toggles between play and pause |
Static Methods:
| Method | Returns | Description |
|---|---|---|
GifView.prefetchAsync(sources: string[]) |
Promise<void> |
Pre-loads GIF data into the native cache |
Known Issues:
nativeRef is typed as React.RefObject<any> — a TODO acknowledges that proper native type definitions should be added.nativeRef.current before calling native methods.Category: Utility
Library: expo-emoji-picker (Custom Expo Module)
Platform: iOS + Android
Summary: Native emoji picker view that fires onEmojiSelected with the selected emoji string when the user taps an emoji.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
onEmojiSelected |
(emoji: string) => void |
Yes | — | Callback fired with the selected emoji character |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
<EmojiPicker
onEmojiSelected={emoji => insertEmoji(emoji)}
style={{ flex: 1 }}
/>
Accessibility:
Category: Utility
Library: expo-scroll-forwarder (Custom Expo Module)
Platform: iOS + Android (native passthrough) / Web (null render)
Summary: Forwards scroll/gesture events from a non-scrollable area to a designated target ScrollView, enabling collapsible headers and custom scroll-linked animations.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
scrollViewTag |
number |
No | — | Native node handle of the target ScrollView |
children |
React.ReactNode |
Yes | — | Content that should forward its scroll events |
Platform-Specific Behavior:
| Behavior | iOS | Android | Notes |
|---|---|---|---|
| Scroll forwarding | Native implementation | Native implementation | Web variant renders null |
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Wrapper component that gates content behind age assurance, rendering an age-restriction notice if the user has not completed age verification.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | The gated screen content |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Allowed | Age assurance passed | Renders children |
| Restricted | Age assurance not passed | Renders age-restriction notice with aaCopy.chatsInfoText |
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Full-width vertical list wrapper with vertical padding, used as the outermost container for all settings screen content.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | Settings items and groups |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
<Layout.Content>
<SettingsList.Container>
<SettingsList.Item>...</SettingsList.Item>
<SettingsList.Divider />
<SettingsList.Group>...</SettingsList.Group>
</SettingsList.Container>
</Layout.Content>
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Grouped settings section that uses a Portal mechanism to render SettingsList.ItemIcon and SettingsList.ItemText in a header row, with remaining children in a content row below.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
iconInset |
boolean |
No | true |
Whether to apply icon-width indentation to the content row |
destructive |
boolean |
No | false |
Whether the group uses destructive styling |
children |
React.ReactNode |
Yes | — | SettingsList.ItemIcon, SettingsList.ItemText, and content items |
Composition:
<SettingsList.Group iconInset={false}>
<SettingsList.ItemText>Sort replies</SettingsList.ItemText>
<Text>Sort replies to the same post by:</Text>
<Toggle.Group type="radio" values={[sort]} onChange={setSort} label="Sort">
<Toggle.Item name="top" label="Top replies first">...</Toggle.Item>
</Toggle.Group>
</SettingsList.Group>
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Horizontal row layout for a single settings entry, providing consistent padding, minimum height, and optional icon inset.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
destructive |
boolean |
No | false |
Whether the item uses destructive styling |
children |
React.ReactNode |
Yes | — | Row content |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Horizontal rule separator between settings groups, with a low-contrast border and vertical margin.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
<SettingsList.Container>
<SettingsList.Item>...</SettingsList.Item>
<SettingsList.Divider />
<SettingsList.Item>...</SettingsList.Item>
</SettingsList.Container>
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Reusable toggle controls for notification channel (push/in-app) and filter (everyone/follows) preferences, used across all notification settings screens.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
Yes | — | The preference key (e.g., "reply", "like", "follow") |
preference |
AppBskyNotificationDefs.Preference | FilterablePreference | undefined |
No | — | The current preference object; undefined shows a loading spinner |
syncOthers |
string[] |
No | [] |
Additional preference keys to update simultaneously |
allowDisableInApp |
boolean |
No | true |
Whether to show the in-app notification toggle |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Loading | preference is undefined |
Centered Loader spinner |
| Channels only | preference is Preference |
Push + in-app toggles |
| With filter | preference is FilterablePreference |
Push + in-app toggles + "From" radio group |
Composition:
<PreferenceControls
name="reply"
preference={preferences?.reply}
allowDisableInApp
/>
Accessibility:
Toggle.Group components have label props for screen reader context.Toggle.Item has a label prop.Known Issues:
onChangeFilter throws new Error('Invalid filter') for unexpected values — not caught within the component.Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Title and subtitle text block used within a settings item, with optional bold/large title styling.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
bold |
boolean |
No | false |
Whether to render the title at text_lg with font_semi_bold |
children |
React.ReactNode |
Yes | — | Title and subtitle content |
Composition:
<SettingsList.Item>
<SettingsList.ItemIcon icon={BellRingingIcon} />
<ItemTextWithSubtitle bold>
<Text>Activity from others</Text>
<Text>Get notified about posts and replies from accounts you choose.</Text>
</ItemTextWithSubtitle>
</SettingsList.Item>
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Tappable hyperlink rendered inline within a block of text, supporting both internal navigation and external URLs.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
to |
string | object |
Yes | — | Navigation destination or URL |
label |
string |
Yes | — | Accessible label for the link |
style |
StyleProp<TextStyle> |
No | — | Additional text styles |
onPress |
(e: GestureResponderEvent) => boolean | void |
No | — | Custom press handler |
children |
React.ReactNode |
Yes | — | Link text content |
Composition:
<Text>
By continuing, you agree to our{' '}
<InlineLinkText to="https://bsky.social/about/support/tos" label="Terms of Service">
Terms of Service
</InlineLinkText>
{' '}and{' '}
<InlineLinkText to="https://bsky.social/about/support/privacy-policy" label="Privacy Policy">
Privacy Policy
</InlineLinkText>.
</Text>
Accessibility:
label prop provides the accessible name for the link.Related Components:
Category: Navigation
Library: Custom
Platform: iOS + Android
Summary: Navigation link component that renders as an anchor element on web and uses React Navigation on native, supporting both internal routes and external URLs.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
to |
string | object |
Yes | — | Navigation destination (route path or object) |
label |
string |
No | — | Accessible label |
accessibilityHint |
string |
No | — | Additional accessibility context |
action |
'push' | 'replace' | 'navigate' |
No | 'push' |
Navigation action type |
children |
React.ReactNode | ((state: { hovered: boolean; pressed: boolean }) => React.ReactNode) |
Yes | — | Link content or render prop |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
<Link to={makeProfileLink(profile)} label={profile.handle}>
<UserAvatar profile={profile} size={42} />
</Link>
Known Issues:
Link from src/view/com/util/Link.tsx is marked @deprecated — should use Link from #/components/Link.tsx.Related Components:
Category: Gesture & Touch
Library: Custom
Platform: iOS + Android
Summary: Pressable button with configurable variant, color, size, shape, and accessible label props, used throughout the application for all interactive button elements.
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
label |
string |
Yes | — | Accessible label (used as accessibilityLabel) |
onPress |
() => void |
No | — | Press callback |
variant |
'solid' | 'outline' | 'ghost' |
No | 'solid' |
Visual style variant |
color |
'primary' | 'secondary' | 'secondary_inverted' | 'negative' | 'primary_subtle' |
No | 'primary' |
Color scheme |
size |
'tiny' | 'small' | 'medium' | 'large' |
No | 'medium' |
Button size |
shape |
'default' | 'round' | 'square' |
No | 'default' |
Border radius shape |
disabled |
boolean |
No | false |
Whether the button is non-interactive |
accessibilityHint |
string |
No | — | Additional accessibility context |
testID |
string |
No | — | Test identifier |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
children |
React.ReactNode | ((state: { hovered: boolean; pressed: boolean }) => React.ReactNode) |
Yes | — | Button content or render prop |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Initial render | Solid background, themed color |
| Hovered | Pointer hover (web) | Slightly darker background |
| Pressed | Touch/click | Darker background |
| Disabled | disabled={true} |
Reduced opacity, non-interactive |
Composition:
<Button
label={_(msg`Save changes`)}
onPress={onSaveChanges}
variant="solid"
color="primary"
size="large"
disabled={!hasUnsavedChanges || isPending}
>
{isPending && <ButtonIcon icon={Loader} />}
<ButtonText>Save changes</ButtonText>
</Button>
Accessibility:
label prop maps to accessibilityLabel.disabled communicates non-interactivity to assistive technologies.Known Issues:
Button from src/view/com/util/forms/Button.tsx is marked @deprecated — should use Button from #/components/Button.tsx.Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Icon slot within a Button component, rendering an icon inline with button text.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
icon |
React.ComponentType |
Yes | — | Icon component to render |
position |
'left' | 'right' |
No | 'left' |
Position relative to button text |
Composition:
<Button label="Add" onPress={onAdd}>
<ButtonIcon icon={PlusIcon} />
<ButtonText>Add</ButtonText>
</Button>
Related Components:
Category: Utility
Library: Custom
Platform: iOS + Android
Summary: Text slot within a Button component, styled to match the button's color and size.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
React.ReactNode |
Yes | — | Button label text |
style |
StyleProp<TextStyle> |
No | — | Additional text styles |
Composition:
<Button label="Sign in" onPress={onSignIn}>
<ButtonText>Sign in</ButtonText>
</Button>
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Bluesky icon mark SVG component, rendered at a configurable width with theme-aware fill color.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
width |
number |
No | 32 |
Width of the logo in logical pixels |
fill |
string |
No | — | Fill color override |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Composition:
<Logo width={76} fill="white" />
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Bluesky wordmark SVG component, rendered at a configurable width with theme-aware fill color.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
width |
number |
No | 91 |
Width of the wordmark in logical pixels |
fill |
string |
No | — | Fill color override |
Composition:
<Logotype width={91} fill="white" />
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Profile picture component with moderation support, live status indicator ring, and type variants (circular for users, rectangular for labelers).
When to Use:
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewBasic | ProfileViewDetailed |
Yes | — | Profile data |
size |
number |
No | 42 |
Avatar size in logical pixels |
moderation |
ModerationDecision |
No | — | Moderation decision for blur/hide |
type |
'user' | 'labeler' |
No | 'user' |
Shape variant (circle vs. rectangle) |
live |
boolean |
No | false |
Whether to show a live status ring |
style |
StyleProp<ViewStyle> |
No | — | Additional styles |
Variants / Visual States:
| State/Variant | Trigger | Visual Behavior |
|---|---|---|
| Default | Normal user | Circular avatar image |
| Labeler | type="labeler" |
Rectangular avatar with rounded corners |
| Live | live={true} |
Red border ring around avatar |
| Moderated | Moderation blur | Avatar may be blurred or hidden |
| Missing | No avatar | Placeholder color/icon |
Composition:
<UserAvatar
profile={profile}
size={88}
moderation={moderation}
type={profile.associated?.labeler ? 'labeler' : 'user'}
live={live.isActive}
/>
Accessibility:
accessibilityRole="image" with a dynamic accessibilityLabel in the profile header context.Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Profile banner image component with moderation support and type variants (user vs. labeler).
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewDetailed |
Yes | — | Profile data |
moderation |
ModerationDecision |
No | — | Moderation decision for blur/hide |
type |
'user' | 'labeler' |
No | 'user' |
Banner type variant |
Composition:
<UserBanner
profile={profile}
moderation={moderation}
type={profile.associated?.labeler ? 'labeler' : 'user'}
/>
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Avatar component with an edit affordance (pencil icon overlay) for use in profile editing dialogs.
Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
AppBskyActorDefs.ProfileViewDetailed |
Yes | — | Profile data |
size |
number |
No | 80 |
Avatar size in logical pixels |
onSelectNewAvatar |
(img: ImageMeta | null) => void |
Yes | — | Callback when a new avatar is selected or removed |
Composition:
<EditableUserAvatar
profile={profile}
size={80}
onSelectNewAvatar={onSelectNewAvatar}
/>
Related Components:
Category: Data Display
Library: Custom
Platform: iOS + Android
Summary: Animated live status indicator overlaid on a user avatar, showing a pulsing "LIVE" badge when a user is currently streaming.
When to Use:
UserAvatar when live.isActive is true.Props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
size |
'small' | 'large' |
No | 'small' |
Size of the indicator |
Composition:
{live.isActive && (
<LiveIndicator size="large" />
)}
Related Components:
Components involved: C-01: Layout.Screen, C-02: Layout.Header.Outer, C-03: Layout.Header.BackButton, C-04: Layout.Header.Content, C-05: Layout.Header.TitleText, C-07: Layout.Header.Slot, C-09: Layout.Content
When to use: For any screen in the application that requires a standard header and scrollable content area.
The pattern:
// Standard screen shell with back button, title, and scrollable content
<Layout.Screen testID="myScreen">
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Screen Title</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
{/* Screen body content */}
</Layout.Content>
</Layout.Screen>
Key rules:
Layout.Screen as the root wrapper.Layout.Header.Slot on the right side when using Layout.Header.BackButton on the left, to keep the title centered.Layout.Content is the scrollable body — do not add a separate ScrollView inside it.<Trans> or _(msg...) for i18n.Components involved: C-84: SettingsList.Container, C-85: SettingsList.Group, C-86: SettingsList.Item, C-87: SettingsList.Divider, C-68: SettingsList.LinkItem, C-69: SettingsList.PressableItem
When to use: For any settings screen that presents a list of configurable options.
The pattern:
<Layout.Content>
<SettingsList.Container>
{/* Navigation link to a sub-screen */}
<SettingsList.LinkItem
to="/settings/account"
label={_(msg`Account`)}
>
<SettingsList.ItemIcon icon={PersonIcon} />
<SettingsList.ItemText>Account</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.Divider />
{/* Action item that opens a dialog */}
<SettingsList.PressableItem
onPress={signOutControl.open}
label={_(msg`Sign out`)}
destructive
>
<SettingsList.ItemIcon icon={SignOutIcon} />
<SettingsList.ItemText>Sign out</SettingsList.ItemText>
</SettingsList.PressableItem>
{/* Grouped items with a shared icon/label header */}
<SettingsList.Group>
<SettingsList.ItemIcon icon={BellIcon} />
<SettingsList.ItemText>Notifications</SettingsList.ItemText>
<Toggle.Item name="push" label={_(msg`Push notifications`)}>
<Toggle.LabelText>Push notifications</Toggle.LabelText>
<Toggle.Platform />
</Toggle.Item>
</SettingsList.Group>
</SettingsList.Container>
</Layout.Content>
Key rules:
SettingsList.Divider to separate logical groups of settings.SettingsList.LinkItem for navigation and SettingsList.PressableItem for actions.SettingsList.Group with the Portal pattern when a group of items shares a common icon and label header.label prop for accessibility.Components involved: C-88: PreferenceControls, C-49: Toggle.Group, C-50: Toggle.Item, C-51: Toggle.Platform, C-52: Toggle.Radio, C-36: Admonition, C-42: Loader
When to use: For any notification settings screen that allows users to configure push and in-app notification channels and optional sender filters.
The pattern:
// In a notification settings screen:
const { data: preferences, isError } = useNotificationSettingsQuery()
<Layout.Content>
<SettingsList.Container>
{/* Header item with icon and description */}
<SettingsList.Item>
<SettingsList.ItemIcon icon={BubbleIcon} />
<ItemTextWithSubtitle bold>
<Text>Replies</Text>
<Text>Get notifications when people reply to your posts.</Text>
</ItemTextWithSubtitle>
</SettingsList.Item>
{/* Error state */}
{isError && (
<Admonition type="error">
<Trans>Failed to load notification settings.</Trans>
</Admonition>
)}
{/* Preference controls (handles loading, channels, and filter) */}
{!isError && (
<PreferenceControls
name="reply"
preference={preferences?.reply}
/>
)}
</SettingsList.Container>
</Layout.Content>
Key rules:
Admonition when isError is true.preference={undefined} to PreferenceControls while loading — it renders a Loader automatically.syncOthers unless multiple preference keys should update together.allowDisableInApp prop defaults to true; set to false only when in-app notifications cannot be disabled for that notification type.Components involved: C-17: List, C-18: ListFooter, C-42: Loader, C-35: EmptyState
When to use: For any screen that displays a paginated list of items fetched from the API.
The pattern:
const { data, isLoading, isError, error, isFetchingNextPage, hasNextPage, fetchNextPage, refetch } =
useMyInfiniteQuery()
const items = useMemo(
() => data?.pages.flatMap(page => page.items) ?? [],
[data]
)
const onEndReached = useCallback(() => {
if (!isFetchingNextPage && hasNextPage && !isError) {
fetchNextPage()
}
}, [isFetchingNextPage, hasNextPage, isError, fetchNextPage])
<List
data={items}
renderItem={({ item }) => <MyItemCard item={item} />}
keyExtractor={item => item.id}
onRefresh={refetch}
refreshing={isPTRing}
onEndReached={onEndReached}
onEndReachedThreshold={4}
ListEmptyComponent={
isLoading ? (
<Loader size="xl" />
) : (
<EmptyState message="No items found." />
)
}
ListFooterComponent={
<ListFooter
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
/>
}
windowSize={11}
/>
Key rules:
onEndReached with !isFetchingNextPage && hasNextPage && !isError to prevent duplicate or error-looping requests.useMemo to flatten paginated pages into a single array.useCallback for onEndReached and renderItem to prevent unnecessary re-renders.ListFooter with isFetchingNextPage and onRetry for pagination error recovery.Components involved: C-79: ScreenTransition, C-44: TextField.Root, C-45: TextField.Input, C-92: Button
When to use: For each step in a multi-step wizard (Starter Pack creation, Find Contacts flow, Onboarding).
The pattern:
// In a wizard step component:
const [state, dispatch] = useWizardState()
<ScreenTransition direction={state.transitionDirection}>
<View style={[a.flex_1, a.px_xl]}>
{/* Step content */}
<TextField.LabelText>
<Trans>Pack name</Trans>
</TextField.LabelText>
<TextField.Root>
<TextField.Input
label={_(msg`Pack name`)}
value={state.name ?? ''}
onChangeText={text => dispatch({ type: 'SetName', name: text })}
placeholder="My starter pack"
/>
</TextField.Root>
</View>
</ScreenTransition>
Key rules:
ScreenTransition with direction={state.transitionDirection}.dispatch from useWizardState() to update shared wizard state — do not use local useState for wizard data.ScreenTransition to state.activeStep when using react-native-reanimated's LayoutAnimationConfig to trigger re-animation on step change.Components involved: C-12: KeyboardAwareScrollView, C-44: TextField.Root, C-45: TextField.Input
When to use: For any form screen where the software keyboard may obscure input fields.
The pattern:
// Form screen with keyboard-aware scrolling
<KeyboardAwareScrollView
keyboardShouldPersistTaps="handled"
keyboardDismissMode="on-drag"
>
<TextField.LabelText>Email</TextField.LabelText>
<TextField.Root>
<TextField.Input
label="Email address"
keyboardType="email-address"
returnKeyType="next"
onSubmitEditing={() => passwordRef.current?.focus()}
autoCapitalize="none"
/>
</TextField.Root>
<TextField.LabelText>Password</TextField.LabelText>
<TextField.Root>
<TextField.Input
ref={passwordRef}
label="Password"
secureTextEntry
returnKeyType="done"
onSubmitEditing={onSubmit}
/>
</TextField.Root>
</KeyboardAwareScrollView>
Key rules:
keyboardShouldPersistTaps="handled" to ensure taps on interactive elements work while the keyboard is open.keyboardDismissMode="on-drag" for natural keyboard dismissal when scrolling.returnKeyType="next" + onSubmitEditing to move focus between fields without dismissing the keyboard.returnKeyType="done" on the last field to trigger form submission.Components involved: C-14: GrowableBanner, C-15: GrowableAvatar, C-13: StatusBarShadow, C-97: UserAvatar, C-98: UserBanner, C-100: LiveIndicator, C-33: RichText
When to use: For the profile header in the Profile screen, combining the banner, avatar, display name, handle, metrics, and bio.
The pattern:
// Profile header shell composition
<ProfileHeaderShell
profile={profile}
moderation={moderation}
isPlaceholderProfile={isPlaceholderProfile}
hideBackButton={hideBackButton}
>
{/* Action buttons row */}
<HeaderStandardButtons
profile={profile}
moderation={moderation}
isMe={isMe}
minimal={minimal}
onFollow={onFollow}
onUnfollow={onUnfollow}
/>
{/* Display name */}
<ProfileHeaderDisplayName profile={profile} moderation={moderation} />
{/* Handle */}
<ProfileHeaderHandle profile={profile} disableTaps={disableTaps} />
{/* Metrics (self only) */}
{isSelf && !isPlaceholderProfile && (
<ProfileHeaderMetrics profile={profile} />
)}
{/* Bio */}
{descriptionRT && !moderation.ui('profileView').blur && (
<RichText value={descriptionRT} numberOfLines={15} enableTags />
)}
</ProfileHeaderShell>
Key rules:
ProfileHeaderShell provides the banner, avatar, back button, and moderation alerts — do not render these separately.isPlaceholderProfile prop hides all interactive content and shows a loading placeholder.RichText) must be hidden when moderation.ui('profileView').blur is true.isSelf).GrowableBanner and GrowableAvatar animations are iOS-only; they degrade gracefully on Android and web.View — React Native's primary layout container. Renders to a native UIView (iOS) or android.view.View (Android). Uses Flexbox for layout with flexDirection defaulting to 'column'. Not equivalent to HTML <div>.
Text — React Native's text rendering primitive. All text must be inside a Text component. Not equivalent to HTML <span> or <p>.
TextInput — React Native's text input primitive. Supports keyboardType, returnKeyType, secureTextEntry, autoCapitalize, autoCorrect, autoComplete, and onSubmitEditing.
ScrollView — React Native's scrollable container. All content is rendered at once (not virtualized). Use FlatList or the custom List component for large datasets.
FlatList — React Native's virtualized list component. Only renders visible items. The custom List component wraps Animated.FlatList from react-native-reanimated.
SectionList — React Native's virtualized list with section headers. Not directly used in the assessed screens; List is used instead.
Image — React Native's image component. The application uses expo-image's Image for better caching and performance.
Modal — React Native's full-screen modal overlay. Used in the Signup Queued screen.
ActivityIndicator — React Native's built-in loading spinner. Used in GrowableBanner for the pull-to-refresh spinner.
StyleSheet — React Native's style definition utility. Not equivalent to CSS classes; styles are JavaScript objects.
SafeAreaView — React Native's safe area container. The application uses useSafeAreaInsets() from react-native-safe-area-context instead.
KeyboardAvoidingView — React Native's keyboard avoidance container. The application uses KeyboardAwareScrollView from react-native-keyboard-controller instead.
StatusBar — React Native's status bar control. The application uses SystemBars from react-native-edge-to-edge and StatusBarShadow for visual treatment.
TouchableOpacity — React Native's touchable with opacity feedback. The application uses the custom Button component and Pressable instead.
Pressable — React Native's flexible touchable. Used internally by the custom Button component.
RefreshControl — React Native's pull-to-refresh control. Used inside List via the onRefresh and refreshing props.
atoms (a) — The application's atomic style token system from #/alf. Provides pre-defined style objects (e.g., a.flex_1, a.px_xl, a.rounded_full) analogous to Tailwind CSS utility classes but for React Native StyleSheet.
useTheme (t) — A hook from #/alf returning the current theme object, including t.palette (raw color values), t.atoms (semantic style objects), and t.name ('light', 'dim', or 'dark').
dim — A third theme mode in addition to light and dark. Bluesky's "dim" mode is a softer dark theme.
platform() — A utility from #/alf that returns different values based on the current platform (native vs. web), similar to React Native's Platform.select().
web() — A utility from #/alf that applies styles only on the web platform, returning null or undefined on native.
native() — A utility from #/alf that applies styles or values only on native (iOS/Android) platforms.
gtMobile — A breakpoint boolean from useBreakpoints() that is true when the viewport is wider than a mobile screen (tablet/desktop).
gtPhone — A breakpoint boolean from useBreakpoints() that is true when the viewport is wider than a phone screen.
FeedDescriptor — A pipe-delimited string identifying a feed: "type|uriOrDid|tab". Examples: "following", "feedgen|at://...", "author|did:plc:...|posts_and_author_threads".
Shadow<T> — A type wrapper from #/state/cache/types representing a locally-cached or optimistically-updated version of a server-fetched object.
ModerationDecision — An @atproto/api object encoding content filtering decisions (blur, hide, warn) for a piece of content based on the user's moderation settings. The .ui(context) method returns display instructions for a specific UI context.
ModerationOpts — Configuration object passed to moderateProfile() containing the viewer's moderation preferences and label definitions.
accessibilityRole — React Native prop that communicates the semantic role of a component to assistive technologies (e.g., "button", "image", "link", "header"). Equivalent to ARIA role on web.
accessibilityLabel — React Native prop providing the accessible name for a component, read aloud by screen readers. Equivalent to aria-label on web.
accessibilityHint — React Native prop providing additional context about a component's behavior, read after the label by screen readers. Equivalent to aria-describedby on web.
accessibilityState — React Native prop communicating the current state of a component (e.g., { selected: true }, { disabled: true }). Equivalent to aria-selected, aria-disabled on web.
accessibilityValue — React Native prop communicating a numeric or text value (e.g., for sliders or progress indicators). Equivalent to aria-valuenow on web.
accessibilityActions — React Native prop defining custom accessibility actions (e.g., magicTap, longpress) that can be triggered by assistive technologies.
accessibilityLiveRegion — React Native prop for dynamic content that should be announced without focus change. Equivalent to aria-live on web.
VoiceOver — Apple's screen reader for iOS and macOS. Activated by triple-clicking the side button on iPhone.
TalkBack — Google's screen reader for Android. Activated via the Accessibility settings menu.
onAccessibilityTap — React Native prop for handling the VoiceOver double-tap gesture on a component.
Accessibility actions — Named actions (e.g., activate, longpress, magicTap) that can be triggered by assistive technologies via accessibilityActions and onAccessibilityAction.
React Native — A framework for building native mobile apps using React and JavaScript. Components render to native views, not HTML DOM elements.
Expo — A platform and set of tools built on top of React Native. The application uses Expo SDK for native modules (expo-image, expo-blur, expo-linear-gradient, expo-clipboard, expo-contacts, expo-updates).
react-native-gesture-handler — A library providing declarative gesture APIs (Gesture.Pan(), GestureDetector, TapGestureHandler). Used for swipe-to-action in GestureActionView.
react-native-reanimated — A library for high-performance animations running on the UI thread via worklets. Used for scroll-driven animations in GrowableBanner, GrowableAvatar, StatusBarShadow, and GestureActionView.
react-native-safe-area-context — Provides useSafeAreaInsets() for device-specific safe area inset values.
react-native-keyboard-controller — Provides KeyboardAwareScrollView for keyboard-aware scroll behavior.
react-native-webview — Provides the WebView component for embedding web content. Used in the Captcha step of the signup flow.
react-native-view-shot — Provides screenshot capture of React Native views. Used in PlaceholderCanvas to generate placeholder avatar images.
expo-image — Expo's optimized image component with caching, transitions, and prefetchAsync(). Used throughout the application in place of React Native's built-in Image.
expo-blur — Expo's BlurView component. Used in GrowableBanner for the overscroll blur effect on iOS.
expo-linear-gradient — Expo's LinearGradient component. Used in StatusBarShadow.
AT Protocol (atproto) — The open, decentralized social networking protocol underlying Bluesky. Defines lexicons (schemas) for posts, feeds, actors, labels, and more. All API calls use AT Protocol XRPC methods.
DID (Decentralized Identifier) — A globally unique, persistent identifier for a Bluesky user account (e.g., did:plc:abc123). Used as the canonical identity key throughout the codebase.
Handle — A human-readable username in the AT Protocol (e.g., alice.bsky.social). Handles can change; DIDs cannot.
AT URI — A URI scheme for AT Protocol resources: at://<did>/<collection>/<rkey>. Used to identify posts, feeds, lists, and other records.
XRPC — The remote procedure call protocol used by AT Protocol. Queries (reads) use GET; procedures (writes) use POST.
Lexicon — AT Protocol's schema definition system. Methods are namespaced (e.g., app.bsky.feed.searchPosts, com.atproto.server.createSession).
TanStack Query (React Query) — The data-fetching and server state management library used throughout the application. Provides useQuery, useInfiniteQuery, useMutation, and useQueryClient.
Lingui — The internationalization (i18n) library used in the application. Trans wraps JSX content for translation, msg marks string literals for extraction, and useLingui provides the _() translation function.
GestureHandlerRootView — Required root wrapper from react-native-gesture-handler that must wrap the entire application for gesture handling to work correctly.
SafeAreaProvider — Required root wrapper from react-native-safe-area-context that provides safe area inset values to all descendant components.
Worklet — A Reanimated concept — a JavaScript function annotated to run on the UI thread rather than the JS thread, enabling frame-synchronous animation updates without bridge overhead.
SharedValue — A Reanimated primitive that holds a value accessible on both the JS thread and the UI thread simultaneously, enabling smooth animations without JS bridge round-trips.
useAnimatedStyle — A Reanimated hook that creates a style object computed on the UI thread, re-evaluated whenever any SharedValue it reads changes.
runOnUI / runOnJS — Reanimated utilities for crossing the JS↔︎UI thread boundary. runOnUI schedules a worklet to run on the UI thread; runOnJS schedules a JS function to be called from a worklet.