Skip to content

letterSpacing property not working properly #54823

Open

Description

@aditya-thummar-devx

Description

When Applying the letterSpacing to style in Text component, it applies the spacing after the character instead of adding space between the characters.

Here is the sample vanila code.

<Text
        style={{
          backgroundColor: 'blue',
          color: 'white',
          fontSize: 18,
          alignSelf: 'center',

          letterSpacing: 30,
          fontVariant: ['tabular-nums'],
        }}
      >
        1/3
      </Text>

Output:

Image

I also tried to apply the fontVariant: ['tabular-nums'], assuming that it may keep the character width same if that's the issue.
But seems that doesn't fix the error.

And this gap keeps increasing when you increase the letterSpacing.

Try 2:

<Text
        style={{
          backgroundColor: 'blue',
          color: 'white',
          fontSize: 18,
          alignSelf: 'center',

          letterSpacing: 90, // <- increased
          fontVariant: ['tabular-nums'],
        }}
      >
        1/3
      </Text>

Output:

Image

And thus How I conclude that it may adds the space after each character instead of gap.

Here is my package.json file's dependencies.

"dependencies": {
    "@medusajs/js-sdk": "2.8.7",
    "@medusajs/types": "2.8.7",
    "@react-native-clipboard/clipboard": "^1.16.3",
    "@react-native-community/geolocation": "^3.4.0",
    "@react-native-documents/picker": "^11.0.0",
    "@react-native-documents/viewer": "^2.0.2",
    "@react-native-firebase/analytics": "^23.4.1",
    "@react-native-firebase/app": "^23.4.1",
    "@react-native-firebase/crashlytics": "^23.4.1",
    "@react-native-firebase/remote-config": "^23.4.1",
    "@react-navigation/bottom-tabs": "^7.4.6",
    "@react-navigation/drawer": "^7.5.7",
    "@react-navigation/native": "^7.1.17",
    "@react-navigation/native-stack": "^7.3.25",
    "@shopify/flash-list": "^2.1.0",
    "@tanstack/react-query": "^5.85.2",
    "dayjs": "^1.11.19",
    "formik": "^2.4.9",
    "i18next": "^25.5.2",
    "jwt-decode": "^4.0.0",
    "lodash": "^4.17.21",
    "patch-package": "^8.0.0",
    "postinstall-postinstall": "^2.1.0",
    "qs": "6.12.0",
    "react": "19.1.0",
    "react-i18next": "^15.7.3",
    "react-native": "0.80.2",
    "react-native-config": "^1.5.6",
    "react-native-fast-image": "^8.6.3",
    "react-native-flash-message": "^0.4.2",
    "react-native-gesture-handler": "^2.28.0",
    "react-native-image-crop-picker": "^0.51.1",
    "react-native-keyboard-aware-scroll-view": "^0.9.5",
    "react-native-keyboard-controller": "^1.19.6",
    "react-native-linear-gradient": "^2.8.3",
    "react-native-mmkv": "^3.3.0",
    "react-native-navigation-bar-color": "^2.0.2",
    "react-native-otp-entry": "^1.8.5",
    "react-native-permissions": "^5.4.4",
    "react-native-reanimated": "^4.0.2",
    "react-native-reanimated-carousel": "^4.0.3",
    "react-native-render-html": "^6.3.4",
    "react-native-safe-area-context": "^5.6.0",
    "react-native-screens": "^4.14.1",
    "react-native-svg": "^15.12.1",
    "react-native-video": "^6.16.1",
    "react-native-worklets": "^0.4.1",
    "yup": "^1.7.1",
    "zustand": "^5.0.7"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@babel/preset-env": "^7.25.3",
    "@babel/runtime": "^7.25.0",
    "@commitlint/cli": "^19.8.1",
    "@commitlint/config-conventional": "^19.8.1",
    "@react-native-community/cli": "19.1.1",
    "@react-native-community/cli-platform-android": "19.1.1",
    "@react-native-community/cli-platform-ios": "19.1.1",
    "@react-native/babel-preset": "0.80.2",
    "@react-native/eslint-config": "0.80.2",
    "@react-native/metro-config": "0.80.2",
    "@react-native/typescript-config": "0.80.2",
    "@tanstack/eslint-plugin-query": "^5.83.1",
    "@types/jest": "^29.5.13",
    "@types/lodash": "^4.17.20",
    "@types/react": "^19.1.0",
    "@types/react-test-renderer": "^19.1.0",
    "babel-plugin-module-resolver": "^5.0.2",
    "cspell": "^9.3.2",
    "eslint": "^8.19.0",
    "eslint-plugin-import": "^2.32.0",
    "husky": "^9.1.7",
    "jest": "^29.6.3",
    "lint-staged": "^16.1.5",
    "prettier": "2.8.8",
    "react-native-dotenv": "^3.4.11",
    "react-test-renderer": "19.1.0",
    "reactotron-react-native": "^5.1.15",
    "typescript": "5.0.4"
  },

Steps to reproduce

  1. Just paste the code blocks I added in the react native project created with same version as mine.

React Native Version

0.80.2

Affected Platforms

Runtime - Android, Runtime - iOS

Output of npx @react-native-community/cli info

info Fetching system and libraries information...
System:
  OS: macOS 26.1
  CPU: (10) arm64 Apple M4
  Memory: 162.77 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.11.0
    path: ~/.nvm/versions/node/v22.11.0/bin/node
  Yarn:
    version: 3.2.3
    path: ~/.nvm/versions/node/v22.11.0/bin/yarn
  npm:
    version: 10.9.0
    path: ~/.nvm/versions/node/v22.11.0/bin/npm
  Watchman: Not Found
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/aditya/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.1
      - iOS 26.1
      - macOS 26.1
      - tvOS 26.1
      - visionOS 26.1
      - watchOS 26.1
  Android SDK: Not Found
IDEs:
  Android Studio: 2025.1 AI-251.26094.121.2512.13840223
  Xcode:
    version: 26.1.1/17B100
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 21.0.8
    path: /usr/bin/javac
  Ruby:
    version: 2.7.5
    path: /Users/aditya/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 19.1.1
    wanted: 19.1.1
  react:
    installed: 19.1.0
    wanted: 19.1.0
  react-native:
    installed: 0.80.2
    wanted: 0.80.2
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: true

info React Native v0.82.1 is now available (your project is running on v0.80.2).
info Changelog: https://github.com/facebook/react-native/releases/tag/v0.82.1
info Diff: https://react-native-community.github.io/upgrade-helper/?from=0.80.2&to=0.82.1
info For more info, check out "https://reactnative.dev/docs/upgrading?os=macos".

Stacktrace or Logs

Not a crash

MANDATORY Reproducer

https://snack.expo.dev/@aditya_thummar/moody-indigo-yogurt

Screenshots and Videos

Image

Activity

  1. Thotaharshavardhani commented on Jan 5, 2026

    @Thotaharshavardhani

    Hello @aditya-thummar-devx , isn't this expected behaviour? as letterSpacing increases the advance width of each glyph (including the last character), as implemented by the underlying native text engines (CoreText on iOS, TextPaint on Android).

  2. aditya-thummar-devx commented on Jan 5, 2026

    @aditya-thummar-devx
    Author

    @Thotaharshavardhani Thank you for your response !!
    Not sure about the behaviour.
    If that's the case then logically it's not right. Because letterSpacing feels like space between the letters, not the width of character.
    So My issue still stands 😅.

  3. JubaKitiashvili commented on Apr 17, 2026

    @JubaKitiashvili

    The mismatch here is that RN measures text widths without applying letterSpacing at the native advance level — iOS TextKit's .kern attribute and Android's Paint.setLetterSpacing both affect rendered advance, but RN's measurement callback doesn't account for that. Result: predicted line heights differ from rendered heights whenever spacing ≠ 0.

    A working approximation at the JS layer: add letterSpacing × codePointCount(segment) to each segment's advance before line-fit. That matches the rendered result within sub-pixel tolerance for most fonts.

    We bundled this into expo-pretext@1.0 (useTextHeight(text, { …, letterSpacing: n }, width)) — cache-keyed on letterSpacing so different values don't collide. Doesn't fix the RN renderer, but at least the measurement side lines up. Happy to help if anyone's stuck.

  4. aditya-thummar-devx commented on Apr 18, 2026

    @aditya-thummar-devx
    Author

    The mismatch here is that RN measures text widths without applying letterSpacing at the native advance level — iOS TextKit's .kern attribute and Android's Paint.setLetterSpacing both affect rendered advance, but RN's measurement callback doesn't account for that. Result: predicted line heights differ from rendered heights whenever spacing ≠ 0.

    A working approximation at the JS layer: add letterSpacing × codePointCount(segment) to each segment's advance before line-fit. That matches the rendered result within sub-pixel tolerance for most fonts.

    We bundled this into expo-pretext@1.0 (useTextHeight(text, { …, letterSpacing: n }, width)) — cache-keyed on letterSpacing so different values don't collide. Doesn't fix the RN renderer, but at least the measurement side lines up. Happy to help if anyone's stuck.

    Thank you for the explanation 😃 , Although I am not that much of experienced enough to know the deep/under layer working. But I think you are referring to the height right?? If that's correct then I am not referring to the height in this issue.
    The issue is, I applied some letterspacing and instead of adding space in between each characters, it just applies space at the end of each character, meaning it may applying width and also the characters are the left side, not centerred. So in result this is not as predicted and as expected.
    Let me know if there is any misunderstanding in this, I'm open for the solutions and help 👍 .

  5. aditya-thummar-devx commented on Apr 18, 2026

    @aditya-thummar-devx
    Author

    @JubaKitiashvili Thanks for the help though, Please check this you are intersted.

  6. JubaKitiashvili commented on Apr 18, 2026

    @JubaKitiashvili

    @aditya-thummar-devx — you're right, the rendering behavior you're describing is a different layer from the measurement gap I was talking about. My comment addressed the case where Text.measure / onLayout reports the wrong height or width when letterSpacing > 0. What you're hitting is the visual distribution of the spacing around each glyph — letterSpacing is appended after each character (including the last) rather than split half-before / half-after. That's baked into NSAttributedString.kern on iOS and Paint.setLetterSpacing on Android; both platforms do it the same way.

    So yeah — that's a native-renderer concern, not something measurement-side can fix. Sorry my comment didn't address your specific case. Worth keeping this open for a real fix from the RN team 🙏.

  7. aditya-thummar-devx commented on Apr 18, 2026

    @aditya-thummar-devx
    Author

    Yeah @JubaKitiashvili for sure, Thank you so much for your attention 🙏.

  8. react-native-bot commented on Oct 15, 2026

    @react-native-bot
    Collaborator

    This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

  9. added
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    and removed
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions