31

Is it possible to add an outline or textShadow to a font in react native to achieve something like this (white font with a black outline):

enter image description here

In CSS on the web its possible to add a text shadow or an outline to a font, to give the text a border that follows the font, something like this:

h1 {
    color: yellow;
    text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
}
<h1>Hello World</h1>

Is it possible to do something similar in react native?

I took the CCS snippet example from this stack overflow post on how to do it with CSS: CSS Font Border?

desertnaut
  • 57,590
  • 26
  • 140
  • 166
Brien Crean
  • 2,599
  • 5
  • 21
  • 46
  • I found this post very helpful: https://stackoverflow.com/questions/32924616/how-to-create-text-border-in-react-native/44454883 There is refer to https://github.com/react-native-community/react-native-svg#text – Snow Oct 15 '19 at 10:59

2 Answers2

17

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

lub0v
  • 801
  • 9
  • 17
  • Thanks!!! This is the only way I found to do this! It's not perfect at 100%, but is the best solution EVER till now! You made my day @lub0v ! – Kiavor Aug 13 '20 at 16:55
  • When you use `text-align: center`, the stroke doesn't match up. Do you know how you would do that? – Sam Aug 29 '20 at 23:50
  • It works even with icons ! Well done – Mike Sep 18 '22 at 17:24
  • This works well. I am looking to have transparency in between instead of fixed white with black borders. Anyway i can achieve that? – Auston Barboza Aug 21 '23 at 05:25
14

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

Shivam Sinha
  • 4,924
  • 7
  • 43
  • 65
  • Thanks for your answer. I'm not sure if my question was clear, but I am not looking to put a square/rectangular border around the text. Rather I am trying to create a shadow effect that follows the contours of the font itself so I can have for example a white coloured font with a black outline – Brien Crean Aug 04 '16 at 00:35
  • 12
    React Native's textShadow* styles halfway achieve what you want. The problem is that the border only renders in one direction, not all around the text. – pmont May 25 '18 at 15:36
  • Did anyone find a solution for this? I'm doing to make my text look just like the picture in the question and am running into the same problem at the shadow only works on direction and its also rendered behind the text and we are trying to outline the text – bzlight Feb 02 '20 at 00:04
  • @bzlight check the answer from me if it is still something you're looking for – lub0v May 03 '20 at 06:53