A flexible View Box with Custom Border Images in React Native

Creating flexible and reusable graphical UI elements in React Native for rich and diverse visual applications. (work in progress)

I’m coming from a web background. When I started working with React Native, it took me a while to get used to the way you need to think when building native apps. Fast forward a few years and now we’ve got way more useful ways to work with images and backgrounds and there are quite a few great UI Frameworks that allow us to pick and match preexisting elements for buttons, cards and many more and hit the ground running when starting a new project.

I recently tried a few of those and had lots of fun building small apps with cards, date pickers, accordions and such. But apparently, the demand for creating dynamic and scaling containers with images as borders has not yet been sated by any UI framework I know of. There are always limitations or simply no support at all. In the end, you need to build those components yourself.

In the course of this article, I’ll run you through the known and often used variations for custom backgrounds in react native and when we’re done with that, I’ll show you how I ended up solving this problem. If you happen to need a bit more JAZZ for your next custom-design-heavy project or game, maybe I can help you out.

If you want to code along, you’ll need npm and the expo-cli globally installed.

To get started, I’m going to use my own expo-starter project (expo/react-native/navigation/redux/linting…) but for this simple tutorial, you might as well just start clean with crna and go from there. If you want to try my expo-starter kit (fancy blueprint background tiles included) head over to my repo on GitHub, clone or download the code to your local machine and run npm i in the root directory. I’m currently using v1.1 at the time of writing this article but newer versions should work the same.

Let’s get started by creating a BoxSimple.jsx as a new component and import it to our SceneHome.jsx

// src/components/BoxSimple.jsx
import React from 'react'
import { View, StyleSheet } from 'react-native'
import PropTypes from 'prop-types'
const BoxSimple = ({ children }) => (
<View style={styles.boxSimple}>
{
children}
</
View>

)
const styles = StyleSheet.create({
boxSimple: {
backgroundColor: '#fff',
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#000',

padding: 10,
margin: 20,
},
})
BoxSimple.propTypes = {
children: PropTypes.node.isRequired,
}
export default BoxSimple

And then we import our new simple box to our SceneHome.jsx and fill it with a <Text> node.

// src/scenes/SceneHome.jsx
import { ImageBackground, Text } from 'react-native'
import { BoxSimple } from '../components/BoxSimple'
// ... some code<ImageBackground ... > <Panel title={`${name} (v${version})`}>
<
NavigationButton
title="go to Settings >>"
target="Settings"
/>
</
Panel>
<BoxSimple>
<
Text>BoxSimple</Text>
</BoxSimple>
</ImageBackground>// ... more code

If you run npm start, the Expo CLI web interface will open in your default browser and you can run the project in your emulator of choice or scan the QR code with your own smartphone if you have the expo app installed.

You should see a plain view with our lorem ipsum and a simple black border / white background.

1. Using an ImageBackground component

This solution comes out of the box. The react native team has listened to public demand and included a background wrapper you can use to place an image behind your content.

Create a BoxImageBackground.jsx (or copy the BoxSimple.jsx) and this time wrap the <Text> in an <ImageBackground>. If you check the assets/ folder, you’ll find a file called assets/sketch_box_01.png for this part. Import the image and set it as the source. Furthermore, you need to supply the component with width and height styles.

Go to the asset folder of my demo repo and download this file if you started with crna instead of my repository. TK

// src/components/BoxImageBackground.jsx
import React from 'react'
import { StyleSheet, ImageBackground } from 'react-native'
import PropTypes from 'prop-types'
import boxImage from '../../assets/sketch_box_01.png'const BoxImageBackground = ({ children }) => (
<ImageBackground
source={boxImage}
style={styles.boxImageBackground}
>
{
children}
</
ImageBackground>

)
const styles = StyleSheet.create({
boxImageBackground: {
width: 160,
height: 120,

padding: 10,
margin: 20,
},
})
BoxImageBackground.propTypes = {
children: PropTypes.node.isRequired,
}
export default BoxImageBackground

Again, add the new component to our SceneHome.jsx and check the result.

// src/scenes/SceneHome.jsx
import {
BoxImageBackground
} from '../components/BoxImageBackground'
// ... some code<ImageBackground ... > <Panel title={`${name} (v${version})`}>
<
NavigationButton
title="go to Settings >>"
target="Settings"
/>
</
Panel>
<BoxImageBackground>
<
Text>Box Image Background</Text>
</
BoxImageBackground>
<BoxSimple>
<
Text>BoxSimple</Text>
</BoxSimple>
</ImageBackground>// ... more code

A serious limitation when working with <ImageBackground> is that you are fixed in your dimensions. You can scale the image by changing the width and height accordingly or the aspect ratio and as you like when you set resizeMode=“stretch” but the result will be … stretched, duh. Not what we would like and certainly something your designer will send you hate-mails for.

If you take a closer look at my SceneHome.jsx, you’ll notice that my page background uses the same technique with a repeating tile

2. Clickable image wrapper

When working with buttons, you can take another provided component, the TouchableOpacity.

You can use TouchableOpacity in addition to ImageBackground. You only have to wrap it around our last component. As an added bonus, the component is visibly clickable with feedback for the user and has a reduced opacity for its content.


// src/components/ButtonTouchableOpacity.jsx
import React from 'react'
import {
ImageBackground,
StyleSheet,
TouchableOpacity,
} from 'react-native'
import PropTypes from 'prop-types'
import buttonImage from '../../assets/sketch_button_01.png'const ButtonTouchableOpacity = ({ children }) => (
<TouchableOpacity>
<ImageBackground
source={buttonImage}
style={styles.buttonTouchableOpacity}
>
{
children}
</
ImageBackground>
</TouchableOpacity>
)
const styles = StyleSheet.create({
buttonTouchableOpacity: {
width: 294,
height: 74,
alignItems: 'center',
justifyContent: 'center',

},
})
ButtonTouchableOpacity.propTypes = {
children: PropTypes.node.isRequired,
}
export default ButtonTouchableOpacity

Don’t forget to add the new <ButtonTouchableOpacity> to our SceneHome.jsx and check for yourself how it feels to touch this button.

3. Styled Components (not working)

The third solution often brought up are styled-components. This package allows you to use regular CSS like annotation as strings to style react-native components and using those you should have access to the border-image attribute to supply a border image via CSS. I couldn’t get this to work when I tried to code a demo. Apparently, border-image is not supported, so slicing an image into 8 segments doesn’t cut it for react-native.

4. 9Patch / CapInsets

There are solutions for some cases where you want to stretch a simple button or chat bubble to adjust to the required size without stretching or blurring in the corners. Using a technique called 9-Patch files (android) or CapInsets (iOS), you can mark the image file used for the background and define areas that can and should be stretched and those that shouldn’t. While iOS has native support for this in the current react-native version, you will need an additional package for android like React Native 9patch image or react-native-image-capinsets.

Working with those you can easily create resizable buttons, banners, tags and chat bubbles, as long as all edges roughly share their dimensions and stretched middle sections are good enough for what you are building. Beyond this point there is only one solution left and that’s the hardest one…

5. Build it yourself

The solution I’ve been using a while now is a bit different as it is a custom component I’ve written myself for those cases. The reason why I did that was the fact that none of the versions above was flexible enough to allow for dynamic height containers or multiple containers using the same design but different dimensions without using multiple versions of the same background image for different sizes or simply stretching the middle sections.

The CSS attribute border-image and 9patch/capInsets work basically by cutting an image into a 3by3 grid and using the 4 corners and 4 sides to adjust the images to the actual size of the container and that’s more or less what I originally set out to replicate myself. In the end, I needed to go even further but that’s another story.

Originally I had different versions for a box with fixed height and flexible width as well as fixed width and flexible height but in the end, I decided to stop using those and drop them from my project. Instead, I’m currently using a single variation with optional configurations and use an object containing the source images and all the necessary parameters for the parts of the frame with their position, size and offset.

This trusty box has served me well for buttons, cards, modals and many other variations in graphics-heavy projects. As soon as I’m working with custom designs that can’t be achieved with styling alone, I’m using my box component.

Slicing a background image is also super easy, especially when working with tools like Photoshop or Gimp. If you open the background image in Photoshop, you can use the helper lined to create a 3 by 3 grid and take the required measurements from there.

Game UI Pack, available at Creative Market

As I had an additional requirement for one of my projects, I needed to include another feature. In Web projects, you have a lot more freedom when using negative margins and absolute positioning to render things outside their parent containers. For native apps, you have to hop through a few loops to get a cross-device result that’s the same. I needed to be able to place ribbons and decorations partly outside the frame. That is why my enhanced box allows for a pseudo margin around the box so that you can take elements from within and pull them out a bit. Sounds more complicated than it actually is and the following example should clear up most of the confusion.

Admittedly, those designs could be made with a simpler solution than my box as the border sections are easily doable with capInsets for example but I chose them to showcase what I’m talking about as my current projects’ designs can’t be shown right now.

Game UI Pack, available at Creative Market with Photoshop Overlay

As you see in this graphic, there is additional padding at the top (yellow) and the ribbon for the content title has been pulled out a bit, allowing for a nice design that’s definitely an eye-catcher and a feature commonly used in both web and game design.

When starting a new project or sometimes even for quick prototyping, I usually import my Box Component and create a set of components using the Box under the surface. That way I can create all the variations I need with one tool and use them wherever I need them in my code. That way, all the setup work with source images and positioning are centralised and I keep my actual template files cleaner, shorter and easier to read.

If you want to use the box or one of my other reusable components in your projects, I’m currently finalising the first release of my rn-gui framework and over the next couple of weeks, I’m gonna add more of my home brewed elements to the framework, one piece at a time, as I still need to add tests, documentation and such before I release them as production-ready to the public. I will set up a GitHub page providing an overview of planned and implemented features and solutions and if you are a patreon of mine, you can even vote on the priority of the planned components or add suggestions to the backlog.

Some words about me:

If you want to see more of my work and progress, feel free to check out my series on building a React Native Web App with Hooks, Bells, and Whistles with Expo SDK33. I’m also currently working on a new series about building more complex and scalable apps using React/Redux, where I’ll go into details about how and why I do stuff the way I do as well as some articles on my experience in building games for web and mobile with React.

And if you feel really supportive right now, you can always support me on patreon, thus allowing me to continue to write tutorials and offer support in the comments section.

I’m a Web / App Developer & father 👨‍👩‍👧 doing freelance and part-time agency work since 2003, 💻 building stuff on the side 🕹 and attending conferences 🎟