React Native font outline / textShadow

There is at least one way to make it look like this on both ios and android:

iPhone simulator screenshot with outlined text

Idea:

The idea is to use multiple shadows on the Text object. We can do it by wrapping Text component with View and clone the same Text object multiple times with different shadows to make them using different directions.

Implementation:

Here is the code for the wrapper component:

import * as React from "react";
import { StyleSheet, View } from "react-native";
import { Children, cloneElement, isValidElement } from "react";

type Props = {
  children: any,
  color: string,
  stroke: number
}
const styles = StyleSheet.create({
  outline: {
    position: 'absolute'
  },
});

export class TextStroke extends React.Component<Props> {
  createClones = (w: number, h: number, color?: string) => {
    const { children } = this.props;
    return Children.map(children, child => {
      if (isValidElement(child)) {
        const currentProps = child.props as any;
        const currentStyle = currentProps ? (currentProps.style || {}) : {};

        const newProps = {
          ...currentProps,
          style: {
            ...currentStyle,
            textShadowOffset: {
              width: w,
              height: h
            },
            textShadowColor: color,
            textShadowRadius: 1
          }
        }
        return cloneElement(child, newProps)
      }
      return child;
    });
  }

  render() {
    const {color, stroke, children} = this.props;
    const strokeW = stroke;
    const top = this.createClones(0, -strokeW * 1.2, color);
    const topLeft = this.createClones(-strokeW, -strokeW, color);
    const topRight = this.createClones(strokeW, -strokeW, color);
    const right = this.createClones(strokeW, 0, color);
    const bottom = this.createClones(0, strokeW, color);
    const bottomLeft = this.createClones(-strokeW, strokeW, color);
    const bottomRight = this.createClones(strokeW, strokeW, color);
    const left = this.createClones(-strokeW * 1.2, 0, color);

    return (
      <View>
        <View style={ styles.outline }>{ left }</View>
        <View style={ styles.outline }>{ right }</View>
        <View style={ styles.outline }>{ bottom }</View>
        <View style={ styles.outline }>{ top }</View>
        <View style={ styles.outline }>{ topLeft }</View>
        <View style={ styles.outline }>{ topRight }</View>
        <View style={ styles.outline }>{ bottomLeft }</View>
        { bottomRight }
      </View>
    );
  }
}

If the text is not big, you can also use only 4 directions instead of 8 to improve performance:

<View>
    <View style={ styles.outline }>{ topLeft }</View>
    <View style={ styles.outline }>{ topRight }</View>
    <View style={ styles.outline }>{ bottomLeft }</View>
    { bottomRight }
</View>

The usage:

<TextStroke stroke={ 2 } color={ '#000000' }>
  <Text style={ {
    fontSize: 100,
    color: '#FFFFFF'
  } }> Sample </Text>
</TextStroke>

You can also use multiple Text objects inside since the wrapper copies all of them.

Performance:

I haven't checked the performance for this solution yet. Since we're copying text so many times it maybe not great.

Issues:

Need to be careful with the stroke value. With higher values, the edges of the shadows will be visible. If you really need a wider stroke, you can fix this by adding more layers to cover different shadow directions.

sample with wide stroke


Yes it is possible through the following properties:

textShadowColor color
textShadowOffset ReactPropTypes.shape( {width: ReactPropTypes.number, height: ReactPropTypes.number} )
textShadowRadius ReactPropTypes.number

https://facebook.github.io/react-native/docs/text.html

Actual completed pull request: https://github.com/facebook/react-native/pull/4975