Styled Components
Styled Components in React Native
Styled Components are a wonderful tool to use
React Native StyleSheets vs. Styled Components
React Native StyleSheets | Styled-Components | |
---|---|---|
Syntax | CSS in JS (e.g. backgroundColor ) |
CSS (as in Web, e.g. background-color) |
Component names | <View style={styles.container} /> |
<Container /> |
Learning curve | CSS in JS easier when used to React Native | CSS easier with existing background in Web-Development |
Template literal syntax has to be learned (eased via template code) | Writing CSS styles as JS objects has to be learned (eased via template code) | |
Theme Creation | Creation of custom theme context necessary | Built-in theme prop (with ThemeProvider context) |
Code Comparison
-
React Native StyleSheet
import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; const Dummy = () => { return ( <View style={[styles.container, styles.rounded]}> <Text>Hello World</Text> </View> ); }; const styles = StyleSheet.create({ container: { borderWidth: 1, borderColor: 'black', borderStyle: 'solid', }, rounded: { borderRadius: 10, }, }); export default Dummy;
-
Styled Components
import React from 'react'; import { Text } from 'react-native'; import styled from 'styled-components/native'; const Dummy = () => { return ( <Container rounded> <Text>Hello World</Text> </Container> ); }; type ContainerProps = { rounded: boolean }; const Container = styled.View<ContainerProps>` border: 1px solid black; border-radius: ${props => (props.rounded ? '10px' : 0)}; `; export default Dummy;
Styled Components with custom components
import React from 'react';
import { View, Text } from 'react-native';
import styled from 'styled-components/native';
const OtherComponent = ({ style }) => {
return (
<View style={{ backgroundColor: 'blue', ...style }}>
<Text>Other Component</Text>
</View>
);
};
const StyledOtherComponent = styled(OtherComponent)`
background-color: red;
`;
export default StyledOtherComponent;
Style components from packages, e.g. LinearGradient
from react-native-linear-gradient
:
const WelcomeScreen: FunctionComponent = () => {
const theme = useContext(ThemeContext);
return (
<Container
colors={['#f26c6c', '#e1f149']}
>
{/* */}
</Container>
);
}
// ...
const Container = styled(LinearGradient)<{ borderRadius?: number }>`
flex: 1;
padding: ${props => props.theme.paddingLarge};
`;
Attributes attrs
ScrollView
Example
In React Native the ScrollView
expects to style the container with a different prop - it can’t be styled as a Styled-Component.
It can be provided via attrs
. In this example the contentContainerStyle
of a ScrollView is styled:
const Container = styled.ScrollView.attrs(props => ({
bounces: false,
showsVerticalScrollIndicator: false,
contentContainerStyle: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
},
}))`
padding: 15px;
`;
TextInput
Example
const Input = styled.TextInput.attrs({ placeholderTextColor: 'rgba(60, 60, 67, 0.3)' })<TextInputPropsT>`
background-color: ${ props => props.error ? 'rgba(193,17,17,0.25)' : '#F5F5F5' };
/* border */
border-radius: 6px;
border-bottom-width: 1px;
border-bottom-color: #E0E0E0;
/* layout */
padding: 0 27px 0 16px;
height: 44px;
/* Font */
font-weight: ${ props => (props.value?.length ?? 0) > 0 ? 600 : 400 };
font-size: 16px;
`;
Own components example
It also works with own components which are prepared for styled-components:
const TitleViewFlexed = styled(TitleView).attrs({
containerStyle: {
marginTop: 32,
},
})`
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
margin-top: -8px;
`;
In TitleView.tsx
the {...props}
in <Content {...props}>{props.children}</Content>
is super important.
Only via this line the styled-components styles set above do style the Content
component of TitleView.tsx
.
// External imports
import React, { FunctionComponent, ReactNode } from 'react';
import { View, StyleProp, ViewStyle } from 'react-native';
import styled from 'styled-components/native';
import InputTitle from './InputTitle';
type TitleViewPropsT = {
containerStyle?: StyleProp<ViewStyle>;
title: string;
children: ReactNode;
};
const TitleView: FunctionComponent<TitleViewPropsT> = (
props: TitleViewPropsT
) => {
return (
<View style={props.containerStyle}>
<InputTitle>{props.title}</InputTitle>
<Content {...props}>{props.children}</Content>
</View>
);
};
const Content = styled.View`
margin-top: 8px;
`;
export default TitleView;
Comment
- Currently when you style this component with styled-components it’s the
Content
which is styled and not the outer part. The outer part you style with thecontainerStyle
prop which accepts a style object. - A different approach for an API here would be to style the outer part with styled-components. Might feel more like what you’d expect and then have a prop
contentStyle
to style whatever is inside.
3-rd party component example
Would also work for external 3-rd party components like DropDownPicker
, i.e.
import DropDownPicker from 'react-native-dropdown-picker';
const Picker = styled(DropDownPicker).attrs({
containerStyle={{ /* ... */ }}
labelStyle={{ /* ... */ }}
/* ...other style values as style objects */
})`
/* styles for styles prop */
`;
css
Create css
style to be used in several styled components, e.g.:
const ContainerStyle = css`
flex-direction: row;
align-items: center;
padding-horizontal: 18px;
padding-vertical: 12px;
`;
const InhalerContainerTouchable = styled.TouchableOpacity`
${ContainerStyle};
flex-direction: column;
`;
Theming
Default theme
Defining a theme
import * as Colors from '../styles/colors';
import * as Fonts from '../styles/fonts';
import * as Layout from '../styles/layout';
const theme = {
// Color
palette: {
common: {
black: Colors.black,
darkGray: Colors.darkGray,
},
primary: Colors.colorPrimary,
secondary: Colors.colorSecondary,
},
// Font
fontSizeSmall: Fonts.fontSizeSmall,
fontSizeMedium: Fonts.fontSizeMedium,
fontSizeLarge: Fonts.fontSizeLarge,
fontWeightLight: Fonts.fontWeightLight,
fontWeightMedium: Fonts.fontWeightMedium,
fontWeightHeavy: Fonts.fontWeightHeavy,
// Layout
borderRadiusSmall: Layout.borderRadiusSmall,
borderRadiusMedium: Layout.borderRadiusMedium,
paddingMedium: Layout.paddingMedium,
paddingLarge: Layout.paddingLarge,
marginMedium: Layout.marginMedium,
marginLarge: Layout.marginLarge,
};
export default theme;
Using the theme prop in a styled component
const Wrapper = styled.TouchableOpacity<Pick<ButtonWidePropsT, 'variant'>>`
${props => {
switch (props.variant) {
case ButtonWideVariant.OUTLINED:
return `
background-color: transparent;
`;
case ButtonWideVariant.WHITE:
default:
return `
background-color: ${props.theme.palette.secondary};
`;
}
}}
/* Layout */
padding: ${props => props.theme.paddingMedium} 0;
margin: ${props => props.theme.marginMedium} 0;
/* Border */
border: ${props => `1px solid ${props.theme.palette.secondary}`};
border-radius: ${props => props.theme.borderRadiusMedium};
/* children */
align-items: center;
`;
Apply theme with ThemeProvider
- Wrap app root component with
ThemeProvider
- Retrieve desired
theme
value from Redux store.
import React, { FunctionComponent } from 'react';
import { StatusBar } from 'react-native';
import { Provider, useSelector } from 'react-redux';
import styled, { ThemeProvider } from 'styled-components/native';
import store from 'store';
import { RootStateT } from 'store/reduxRoot';
import { SettingsT } from 'store/settings/types';
import IntroScreen from 'screens/IntroScreen';
/**
* Main part of the app - redux is accessible here
*/
const AppMain = () => {
const { theme } = useSelector<RootStateT, SettingsT>(state => state.settings);
return (
<ThemeProvider theme={theme}>
<IntroScreen />
</ThemeProvider>
);
};
/**
* Shell around the app which injects the global redux store
*/
const App: FunctionComponent = () => (
<Container>
<StatusBar barStyle="light-content" />
<Provider store={store}>
<AppMain />
</Provider>
</Container>
);
const Container = styled.View`
flex: 1;
`;
export default App;
Flavors
Create a flavor by extending the default theme.
import produce from 'immer';
import defaultTheme from './default';
/**
* New flavor theme extends the default theme
*/
const theme = produce(defaultTheme, draft => {
draft.palette.primary = '#2b762c';
draft.palette.secondary = '#5dfc5f';
});
export default theme;
The immer.js
library here allows to just declare the differences to the defaultTheme
. immer.js
’s produce
function takes care of merging the themes as desired.
Theme in normal components
Sometimes you may want to use the colors you defined in a theme inside of a regular component as opposed to a styled component.
In a styled component you can easily retrieve the theme via props.theme
. However, in a regular component you have to retrieve the theme
via React context:
import React, { useContext } from 'react';import { ThemeContext } from 'styled-components/native';
const WelcomeScreen: FunctionComponent = () => {
const theme = useContext(ThemeContext);
return (
<LinearGradient
colors={[theme.palette.gradient.start, theme.palette.gradient.end]} >
{/* ... */}
</LinearGradient>
);
}
We use the useContext
hook to retrieve the ThemeContext
which is defined via
Theme and TypeScript
See this great articles https://medium.com/rbi-tech/theme-with-styled-components-and-typescript-209244ec15a3 and the new code!
Create app/styled.d.ts
declaration file for code completion support in VSCode.
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme {
// Color
palette: {
common: {
black: string;
darkGray: string;
};
primary: string;
secondary: string;
};
// Font
fontSizeSmall: string;
fontSizeMedium: string;
fontSizeLarge: string;
fontWeightLight: number;
fontWeightMedium: number;
fontWeightHeavy: number;
// Layout
borderRadiusSmall: string;
borderRadiusMedium: string;
paddingMedium: string;
paddingLarge: string;
marginMedium: string;
marginLarge: string;
}
}
Discuss on Twitter ● Improve this article: Edit on GitHub