ARTICLE
In-Depth Styling with React Native and Flexbox
From React Native in Action by Nader Dabit
__________________________________________________________________
Save 37% off React Native in Action with code fccdabit at manning.com.
__________________________________________________________________
This article goes deep into styling with React Native and Flexbox.
Using Flexbox to layout components
Flexbox is a layout implementation that React Native uses to provide an efficient way for users to create UIs and control positioning in React Native. The React Native Flexbox implementation is based on the W3C Flexbox web specification but it doesn’t share 100% of the API. It aims to give us an easy way to reason about, align and distribute space among items in our layout, even when their size is unknown or even when their size is dynamic. Flexbox layout’s only available for use on View
components.
You’ve already seen Flexbox used in many of the examples. It makes laying out items much easier than alternative methods; it’s hard not to use it. It’s powerful. It will benefit you greatly to take your time and understand the material in this section.
Here are the alignment properties that you’ll use to control the Flexbox layout:
- flex
- flexDirection
- justifyContent
- alignItems
- alignSelf
- flexWrap
Altering a component’s dimensions with flex
The flex
property specifies the ability of a component to alter its dimensions to fill the space of the container it’s in. This value is relative to the flex properties specified for the rest of the items within the same container.
This means that if we have a View
element with a height of 300 and a width of 300, and a child View
with a property of flex: 1
, then the child view completely fills the parent view. If we decide to add another child element with a flex property of flex: 1
, they’ll each take up equal space within the parent container.
Another way to look at this is to think of the flex
properties as being percentages. For example, if you want your child components to take up 66.6% and 33.3% respectively, you could use flex:66
and flex:33
. The first item occupies two-thirds, and the second item occupies one-third of the parent container. The flex
number is only important relative to the other flex
items occupying the same space. Thinking of flex numbers as percentages makes it much easier to comprehend. Rather than flex:66
and flex:33
, you could have specified flex:2
and flex:1
and achieved the same layout effect.
To better understand how this all works, let’s take a look at a few examples shown in figure 1.
This is easily achieved by setting the appropriate flex
value on the individual elements. Listing 1 shows you the exact steps necessary to create such a layout.
Listing 1 Flex views with a 1:2 ratio, 1:3 ratio, and a 1:4 ratio.
…
render() {
return (
<View style={styles.container}>
<View style={[styles.flexContainer]}>
<Example style={[{flex: 1},styles.darkgrey]}>A 50%</Example> #A
<Example style={[{flex: 1}]}>B 50%</Example>
</View>
<View style={[styles.flexContainer]}> #B
<Example style={[{flex: 1},styles.darkgrey]}>C 33%</Example>
<Example style={{flex: 2}}>D 66%</Example>
</View>
<View style={[styles.flexContainer]}> #C
<Example style={[{flex: 1},styles.darkgrey]}>E 25%</Example>
<Example style={{flex: 3}}>F 75%</Example>
</View>
</View>
);
}
…
#A The items have the same flex value, and they take up the same amount of space within their parent container.
#B C takes up 1/3 of the total space, and D takes up 2/3 of the total space.
#C E takes up 1/4 of the total space, and F takes up 3/4 of the total space.
Specifying the direction of the flex with flexDirection
You may notice that the items in our flex containers are laying out in a column (Y-axis), meaning from top to bottom. A is stacked on B, C is stacked on D, and E is stacked on F. Using the flexDirection
property, we can change the primary axis of the layout, and therefore change the direction of the layout. flexDirection
is applied to the parent view that contains child flex views.
flexDirection
has been set to ‘row’. Now the items take up space horizontally within the row rather than vertically within the column.All you need to achieve the layout in figure 2 is to add a single line of code to the flexContainer
style, which is the parent container for each of the example components. Changing the flexDirection
on this container affects the layout of all its flex children. Add flexDirection: 'row'
to the style and see how it changes the layout.
Listing 2 Adding flexDirection: ‘row’ to the parent container
flexContainer: { #A
width: 150,
height: 150,
borderWidth: 1,
margin: 10,
flexDirection: 'row' #B
},
#A flexContainer
is the parent container of each example
#B Adding flexDirection: 'row'
to the parent container causes the children to be laid out horizontally.
Two options for flexDirection are
row
and column
. The default setting is column
. If you don’t specify a flexDirection
property, your content will be laid out in a column.
As you can see, the child elements now lay out left to right. This property is something that you’ll use a lot when developing apps in React Native; it’s important to grasp it and understand how it works.
Defining how space is used around a component with justifyContent
Using the flex property, you can specify how much space each component takes up within its parent container, but what if you’re not trying to take up the entire space? How can you use Flexbox to lay out components using their original size?
justifyContent
defines how space is distributed between and around flex items along the primary axis of the container (the flex-direction). justifyContent
is declared on the parent container. Five options are available:
- center
- flex-start
- flex-end
- space-around
- space-between
Specifying center
causes the children to be centered within the parent container. The free space is distributed on both sides of the clustered group of children.
flex-start
groups the components at the beginning of the flex column
or row
depending upon what value is assigned to flexDirection
. flex-start
is the default value for justifyContent
.
flex-end
acts in the opposite manner. It groups items together at the end of the container.
space-around
attempts to evenly distribute space around each element. Don’t confuse this with distributing the elements evenly in the container; the space is being distributed around the elements. If it were based on the elements you’d expect: “space – element – space – element – space”. Instead Flexbox allocates the same amount of space on each side of the element, yielding: “space – element – space – space – element – space”. In both cases, the amount of whitespace is the same, but in the latter, the space between elements ends up being greater.
space-between
doesn’t apply spacing at the start or end of the container. The space between any two consecutive elements is the same as the space between any other two consecutive elements.
Figure 3 demonstrates how each of the justifyContent
properties distribute space between and around the flex elements. Every example uses two elements to help depict what’s happening.
Listing 3 is the code used to generate figure 3. Have a look at it in detail to understand how it works, then try to do a couple things on your on: add more elements to each example to see what happens as the number of items increases and set the flexDirection
to row
to see what happens when the items are laid out horizontally instead of vertically.
Listing 3 Examples showing each of the justifyContent options
...
render() {
return (
<View style={styles.container}>
<FlexContainer style={[{justifyContent: 'center'}]}> #A
<Example>center</Example>
<Example>center</Example>
</FlexContainer>
<FlexContainer style={[{justifyContent: 'flex-start'}]}> #B
<Example>flex-start</Example>
<Example>flex-start</Example>
</FlexContainer>
<FlexContainer style={[{justifyContent: 'flex-end'}]}> #C
<Example>flex-end</Example>
<Example>flex-end</Example>
</FlexContainer>
<FlexContainer style={[{justifyContent: 'space-around'}]}> #D
<Example>space-around</Example>
<Example>space-around</Example>
</FlexContainer>
<FlexContainer style={[{justifyContent: 'space-between'}]}> #E
<Example>space-between</Example>
<Example>space-between</Example>
</FlexContainer>
</View>
);
}
...
#A An example of how to use the justifyContent: ‘center’ option.
#B An example of how to use the justifyContent: ‘flex-start’ option.
#C An example of how to use the justifyContent: ‘flex-end’ option.
#D An example of how to use the justifyContent: ‘space-around’ option.
#E An example of how to use the justifyContent: ‘space-between’ option.
Aligning children in a container with alignItems
alignItems
defines how to align children along the secondary axis of its container. This property is declared on the parent view and affects its flex children as flexDirection
does. Four possible values for alignItems are
:
- stretch
- center
- flex-start
- flex-end
stretch
is the default alignment, and it’s what we’ve seen in the figures 2 and 3. Each example component’s stretched to fill its parent container. Let’s revisit Figure 1 and see what happens when we use the other options: center
, flex-start
, and flex-end
.
alignItems
properties: center, flex-start, and flex-end.Because we haven’t specified a precise width for any of the example components, they only take up as much space horizontally as is necessary to render their contents rather than stretch them to fill the entire space. In the first case, alignItems
is set to 'center'
. In the second case alignItems
is set to 'flex-start'
. In the final case, alignItems
is set to 'flex-end'
.
Use the code snippet from listing 4 to change the alignments on each of the examples from listing 1.
Listing 4 Using non-default alignItems properties: center, flex-start, and flex-end
render() {
return (
<View style={styles.container}>
<View style={[styles.flexContainer,{alignItems: 'center'}]}> #A
<Example style={[styles.darkgrey]}>A 50%</Example>
<Example>B 50%</Example>
</View>
<View style={[styles.flexContainer,{alignItems: 'flex-start'}]}> #B
<Example style={[styles.darkgrey]}>C 33%</Example>
<Example style={{flex: 2}}>D 66%</Example>
</View>
<View style={[styles.flexContainer,{alignItems: 'flex-end'}]}> #C
<Example style={[styles.darkgrey]}>E 25%</Example>
<Example style={{flex: 3}}>F 75%</Example>
</View>
</View>
);
}
#A Changing the alignItems property to center.
#B Changing the alignItems property to flex-start.
#C Changing the alignItems property to flex-end.
Now that you’ve seen how to use the other alignItems
properties and their effects on the default column layout, why don’t you go ahead and set the flexDirection
to 'row'
and see what happens?
Overriding the parent container’s alignment with alignSelf
This far, all of the flex properties have been applied to the parent container. alignSelf
is applied to an individual flex child directly.
With alignSelf
, we can get access to the alignItems
property for individual elements within the container
. alignSelf
gives us the ability to override whatever alignment was set on the parent container, and allows a child object to be aligned independently of its peers. The available options are:
- auto
- stretch
- center
- flex-start
- flex-end
The default value for alignSelf
is auto
. auto
takes the value from the parent container’s alignItems
setting. The remaining properties: stretch
, center
, flex-start
, and flex-end
, affect the layout in the same way as their corresponding properties on alignItems
.
In figure 5, the parent container doesn’t have an alignItems
value set, and it defaults to stretch
. You can see in the first example that the auto
value inherits stretch
from its parent container. The next four examples: stretch
, center
, flex-start
, and flex-end
, layout exactly as you’d expect. The final example has no alignSelf
property set, it defaults to auto,
and it’s laid out the same as the first example.
alignSelf
properties affect the layout when their parent container’s alignItems
property is set to the default value of stretch
.In listing 5, I’ve done something a little different. Rather than supply the style directly to the Example
element, I created a new component property align
. You can see how align
is passed down to the Example
component and is used to set the alignSelf
property. Otherwise, the example is the same as many others in this article; it explores the effects of each value applied to the style.
Listing 5 Using alignSelf to override the parent container’s alignItems property
import React, { Component } from 'react';
import { StyleSheet, Text, View} from 'react-native';
export default class App extends Component<{}> {
render() {
return (
<View style={styles.container}>
<FlexContainer style={[]}>
<Example align='auto'>auto</Example> #A
<Example align='stretch'>stretch</Example> #B
<Example align='center'>center</Example> #C
<Example align='flex-start'>flex-start</Example> #D
<Example align='flex-end'>flex-end</Example> #E
<Example>default</Example> // F
</FlexContainer>
</View>
);
}
}
const FlexContainer = (props) => (
<View style={[styles.flexContainer,props.style]}>
{props.children}
</View>
);
const Example = (props) => (
<View style={[styles.example,
styles.lightgrey,
{alignSelf: props.align || 'auto'}, #G
props.style
]}>
<Text>
{props.children}
</Text>
</View>
);
const styles = StyleSheet.create({
container: {
marginTop: 50,
alignItems: 'center',
flex: 1
},
flexContainer: {
backgroundColor: '#ededed',
width: 120,
height: 180,
borderWidth: 1,
margin: 10
},
example: {
height: 25,
marginBottom: 5,
backgroundColor: '#666666'
},
});
#A Setting alignSelf
to auto
picks up the parent container’s value of stretch.
#B Setting alignSelf
explicitly to stretch
.
#C Set the example’s alignSelf
property to center
.
#D Set the example’s alignSelf
property to flex-start
.
#E Set the example’s alignSelf
property to flex-end
.
#F The default value for alignSelf
is auto.
#G The align
property is used to set the Example
component’s alignItems
style.
Preventing clipped items with flexWrap
You learned earlier that the flexDirection
property takes two values, column
(the default) and row
. You saw how column
laid out items vertically and how row
laid out items horizontally. What I didn’t show you was a situation in which items flowed off the screen because they didn’t fit.
flexWrap
takes two values: nowrap
and wrap
. The default value is nowrap
, meaning that items flow off the screen if they don’t fit. The items are clipped, and the user can’t see them. To work around this problem, we use the wrap
value.
flexWrap
set to nowrap
and the other with flexWrap
set to wrap
.As you can see in Figure 6, the first example uses nowrap
, and the squares flow off of the screen. The row of squares is chopped off at the right edge, and you can’t see anything after that point. The second example uses wrap, and the squares wrap around and start a new row.
Listing 6 Example of how the flexWrap values nowrap and wrap affect layout
import React, { Component } from 'react';
import { StyleSheet, Text, View} from 'react-native';
export default class App extends Component<{}> {
render() {
return (
<View style={styles.container}>
<NoWrapContainer> #A
<Example>A nowrap</Example>
<Example>1</Example>
<Example>2</Example>
<Example>3</Example>
<Example>4</Example>
</NoWrapContainer>
<WrapContainer> #B
<Example>B wrap</Example>
<Example>1</Example>
<Example>2</Example>
<Example>3</Example>
<Example>4</Example>
</WrapContainer>
</View>
);
}
}
const NoWrapContainer = (props) => (
<View style={[styles.noWrapContainer,props.style]}> #C
{props.children}
</View>
);
const WrapContainer = (props) => (
<View style={[styles.wrapContainer,props.style]}> #D
{props.children}
</View>
);
const Example = (props) => (
<View style={[styles.example,props.style]}>
<Text>
{props.children}
</Text>
</View>
);
const styles = StyleSheet.create({
container: {
marginTop: 150,
flex: 1
},
noWrapContainer: {
backgroundColor: '#ededed',
flexDirection: 'row', #E
flexWrap: 'nowrap',
borderWidth: 1,
margin: 10
},
wrapContainer: {
backgroundColor: '#ededed',
flexDirection: 'row', #F
flexWrap: 'wrap',
borderWidth: 1,
margin: 10
},
example: {
width: 100,
height: 100,
margin: 5,
backgroundColor: '#666666'
},
});
#A The first example with flexWrap
set to nowrap
. The squares overflow off the screen.
#B The second example with flexWrap
set to wrap
. The row of squares wraps around to start a new line rather than overflowing off the screen.
#C Using the noWrapContainer
style for the first example.
#D Using the wrapContainer
style for the second example.
#E The noWrapContainer
sets the flexDirection
to row and flexWrap
to nowrap
.
#F The wrapContainer
sets the flexDirection
to row and flexWrap
to wrap
.
It’s easy to see which behavior is more preferable when laying out tiles, but you may come across a situation in which nowrap
serves you better. Either way, you should now have a clear understanding of Flexbox and the many ways in which it can help you build responsive layouts in React Native.
That’s all for now. For more insight into React Native, check out the book on liveBook here and see this slide deck.
About the author:
Nader Dabit has been developing with React Native since the framework’s release and is active in the React Native community.
Originally published at freecontent.manning.com.