Skip to content

feat(Android, Stack v5): add title/subtitle font customization - #4415

Open
kligarski wants to merge 3 commits into
@kligarski/stack-v5-android-header-subtitlefrom
@kligarski/stack-v5-android-header-title-appearance
Open

feat(Android, Stack v5): add title/subtitle font customization#4415
kligarski wants to merge 3 commits into
@kligarski/stack-v5-android-header-subtitlefrom
@kligarski/stack-v5-android-header-title-appearance

Conversation

@kligarski

@kligarski kligarski commented Jul 28, 2026

Copy link
Copy Markdown
Member

Description

Adds support for customizing text appearance for title and subtitle in header.

Closes https://github.com/software-mansion/react-native-screens-labs/issues/909.
Closes https://github.com/software-mansion/react-native-screens-labs/issues/910.

Typeface resolution

In order to keep separation between React and native side, I decided to handle the font as follows:

  1. Applicator resets the font to Material's default.
  2. Applicator asks the ConfigurationProviding to apply any changes to the default font via titleTypefaceTransform function.
  3. HeaderConfig which implements ConfigurationProviding uses ReactTypefaceUtils.applyStyles to modify the font family, style and weight and returns modified Typeface
  4. Applicator applies the returned Typeface

Changes

  • add props and handle them on the native side:
    • small header: {title,subtitle}{Color,FontFamily,FontSize,FontWeight,FontStyle}
    • medium/large header: {collapsed,Expanded}{Title,Subtitle}{Color,FontFamily,FontSize,FontWeight,FontStyle}
  • add TITLE_APPEARANCE invalidation flag to StackHeaderConfig
  • add StyleUtils.resolveStyleResAttr to resolve default text appearance
  • add View.spToPx extension that allows to convert SP to pixels with respect to view's display metrics
  • add invalidatingProperty util to StackHeaderConfig for props that invalidate a flag on diff
  • add SFT

Before & after - visual documentation

small medium/large
small.mp4
large.mp4

Test plan

Run test-stack-header-title-appearance-android.

Checklist

  • Included code example that can be used to test this change.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Android Stack v5 (Material 3) header support for customizing title/subtitle text appearance (color, size, family, weight, style) for both small headers and expanded/collapsed states in medium/large collapsing headers, wiring new JS props through Fabric to native applicators and providing a manual SFT scenario to validate behavior.

Changes:

  • Exposes new android props for title/subtitle appearance (small + expanded/collapsed variants) and stringifies fontWeight on the JS side for the native setter contract.
  • Implements Android-side appearance application with a new TITLE_APPEARANCE invalidation flag, default text-appearance reset via theme attrs, and typeface transforms via ReactTypefaceUtils.
  • Adds utilities (resolveStyleResAttr, View.spToPx) and a new single-feature-test scenario for manual verification.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/fabric/stack/StackHeaderConfigAndroidNativeComponent.ts Adds new native props for title/subtitle appearance (small + expanded/collapsed variants).
src/components/stack/header/StackHeaderConfig.android.types.ts Updates public TS types/docs to expose new Android header appearance props.
src/components/stack/header/StackHeaderConfig.android.tsx Converts fontWeight values to strings before passing to native.
apps/src/tests/single-feature-tests/stack-v5/test-stack-header-title-appearance-android/scenario.md Adds manual test scenario documentation for verifying appearance behavior.
apps/src/tests/single-feature-tests/stack-v5/test-stack-header-title-appearance-android/scenario-description.ts Registers scenario metadata for the new SFT.
apps/src/tests/single-feature-tests/stack-v5/test-stack-header-title-appearance-android/index.tsx Implements the interactive SFT UI to exercise all appearance props.
apps/src/tests/single-feature-tests/stack-v5/index.ts Exports/registers the new scenario in the Stack v5 SFT group.
android/src/main/java/com/swmansion/rnscreens/utils/StyleUtils.kt Adds helper to resolve theme style attributes to concrete style resource IDs.
android/src/main/java/com/swmansion/rnscreens/utils/DimensionUtils.kt Adds View.spToPx for font-size conversion respecting view display metrics/font scale.
android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderCoordinatorLayout.kt Hooks new TITLE_APPEARANCE invalidation into the update pipeline.
android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderApplicator.kt Applies title/subtitle appearance (reset-to-default + overrides) for small and collapsing headers.
android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderInvalidationFlags.kt Introduces TITLE_APPEARANCE flag and includes it in APPEARANCE.
android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigViewManager.kt Adds setters for all new appearance props, parsing weight/style via ReactTypefaceUtils.
android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigurationProviding.kt Extends config contract with appearance-related values and TypefaceTransform.
android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfig.kt Stores new appearance props, builds TypefaceTransforms, and adds invalidatingProperty helper.
android/src/main/java/com/swmansion/rnscreens/stack/header/appbar/StackHeaderAppBarLayout.kt Forces creation of stable title/subtitle TextView references for small header toolbars.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@kligarski
kligarski marked this pull request as ready for review July 28, 2026 14:27
Comment on lines +53 to +56
toolbar.title = FORCE_CREATE_PLACEHOLDER
val title = toolbar.toolbarTextViews().single()
toolbar.subtitle = FORCE_CREATE_PLACEHOLDER
val subtitle = toolbar.toolbarTextViews().first { it !== title }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're clearing the text anyway, won't it work to have two distinct placeholders rather than these checks ?

internal var titleFontWeight: Int by invalidatingProperty(ReactConstants.UNSET, StackHeaderInvalidationFlags.TITLE_APPEARANCE)
internal var titleFontStyle: Int by invalidatingProperty(ReactConstants.UNSET, StackHeaderInvalidationFlags.TITLE_APPEARANCE)
override val titleTypefaceTransform: TypefaceTransform?
get() = buildTypefaceTransform(titleFontFamily, titleFontWeight, titleFontStyle)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't it allocate a new lambda object on every call?

Comment on lines +169 to +177
override var subtitleColor: Int? by invalidatingProperty(null, StackHeaderInvalidationFlags.TITLE_APPEARANCE)
internal set
override var subtitleFontSize: Float? by invalidatingProperty(null, StackHeaderInvalidationFlags.TITLE_APPEARANCE)
internal set
internal var subtitleFontFamily: String? by invalidatingProperty(null, StackHeaderInvalidationFlags.TITLE_APPEARANCE)
internal var subtitleFontWeight: Int by invalidatingProperty(ReactConstants.UNSET, StackHeaderInvalidationFlags.TITLE_APPEARANCE)
internal var subtitleFontStyle: Int by invalidatingProperty(ReactConstants.UNSET, StackHeaderInvalidationFlags.TITLE_APPEARANCE)
override val subtitleTypefaceTransform: TypefaceTransform?
get() = buildTypefaceTransform(subtitleFontFamily, subtitleFontWeight, subtitleFontStyle)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be the same set of props as for the title - shouldn't we consider some grouping inside a dedicated object and operate on e.g. title/subtitleAppearance objects on the native side?


titleColor?: ColorValue | undefined;
titleFontFamily?: string | undefined;
titleFontSize?: CT.WithDefault<CT.Float, -1>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: codegen handles that default properly, but in other places we're using CT.WithDefault<CT.Float, -1.0>;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants