Skip to main content

Mobile styles and themes

For styling and theme support we use the ThemedStyles service. This service provides some frequently used styles, for common layouts, colors, font size, and spacing (padding and margin).

Using styles

<Text style={ThemedStyles.style.colorWhite} />
<Text style={ThemedStyles.style.colorPrimaryText} />

Combining styles

Before diving into the different styles that the service provides, it is important to understand how to combine them, that it returns a stable reference to the styles, and the implications of this.

Combine

With the method combine you can merge many styles into one.

Standard styles

const fontStyle = ThemedStyle.combine("colorWhite", "fontXL", "marginTop");

Custom styles

const myStyle = ThemedStyle.combine(
{ heigh: 20 },
"flexContainer",
"marginTop",
"bgBlack"
);

Stable reference to styles

ThemedStyles returns always a stable reference to the styles, this allows you to use a "global" defined style for a component and still be able to switch themes in runtime. It is optimum because you share the same style with all the instances of the component and you ensure that you are sending always the same style object (preventing unneeded re-renders).

function MyText() {
return <Text style={fontStyle}>Title</Text>;
}

const fontStyle = ThemedStyle.combine(
"colorPrimaryText",
"fontXL",
"marginTop"
);

useStyle hook

If you need a style based on a runtime value you can use the useStyle hook.

function MyText() {
const inset = useSafeAreaInsets();
const fontStyle = useStyle("colorPrimaryText", "fontXL", {
paddingTop: inset.top,
});
return <Text style={fontStyle}>Title</Text>;
}

It returns a stable reference to the style object but in this case you will have one style for each instance of the component.

Styles

Colors

You can use colors for text, backgrounds, borders and shadows using the following style format:

Text

color[ColorName]

<Text style={ThemedStyles.style.colorWhite} />

Background

bg[ColorName]

<View style={ThemedStyles.style.bgPrimaryBackground} />

Border

bg[ColorName]

<View style={ThemedStyles.style.bcolorLink} />

Shadow

bg[ColorName]

<View style={ThemedStyles.style.shadowLink} />

You can check the available colors or add new ones on src/styles/Colors.ts.

Spacing

For spacing you can use the margin and padding styles with the following format:

margin(Top|Bottom|Right|Left|Vertical|Horizontal)(n)x padding(Top|Bottom|Right|LeftVertical|Horizontal)(n)x

Eg: | Style | | |-------------------|---------------------------------------------| | padding | padding of 4 | margin | margin of 4 | padding2x | padding of 8 | margin4x | margin of 16 | marginTop4x | margin of 16 to top the view |paddingVertical2x | padding of 8 into the top and bottom |paddingHorizontal | padding of 4 into left and right

function MyComponent(props) {
return (
<View style={containerStyle}>
<Text style={ThemedStyle.style.marginTop}>{user.me.name}</Text>
<Text style={ThemedStyle.style.marginTop}>{user.me.username}</Text>
</View>
);
}

const containerStyle = ThemedStyle.combine("paddingTop2x", "marginHorizontal");

For now it uses a multiple of 5 but we will introduce a multiplier based on the screen size.

Border

The border styles have the following format: border(Top|Bottom|Left|Right|Radius)(n)x

function MyComponent(props) {
return <View style={containerStyle} />;
}

const containerStyle = ThemedStyle.combine(
"borderTop",
"borderBottom5x",
"borderRadius3x"
);

For hair width: borderHair, borderLeftHair, borderRightHair, borderTopHair borderTopHair

Sizes

For width and height percentage you can use the following styles fullWidth halfWidth fullHeight halfHeight

or

width(n) height(n)

Eg:

function MyComponent(props) {
return (
<View style={ThemedStyle.style.width35}/> // width: 35%
<View style={ThemedStyle.style.height56}/> // height: 56%
<View style={ThemedStyle.style.fullWidth}/> // width: 100%
<View style={ThemedStyle.style.fullHeight}/> // height: 100%
);
}

Font

You can use the following normalized font style for size and weight

Sizing

Normalized font size: **fontS fontXS fontM fontL fontXL fontXXL fontXXXL`

Weight

Normalized font weight: fontHairline fontThin fontLight fontNormal fontMedium fontSemibold fontBold

Align

Normalized font weight: textRight textLeft textCenter textJustify

Other

strikeThrough

function MyComponent() {
return <Text style={fontStyle}>Title</Text>;
}

const fontStyle = ThemedStyle.combine(
"fontL",
"fontLight",
"colorWhite",
"textCenter"
);

Layout

Usually, you will use custom styles in your component to define the main layout, but there are some commonly used layout styles that are available for you in the themed styles service.

Eg: | Style | |-------------------| |flexContainer | |flexContainerCenter | |flexColumn | |flexColumnCentered | |rowJustifyEnd | |rowJustifyCenter | |rowJustifySpaceEvenly | |rowJustifyStart | |rowJustifySpaceBetween | |rowStretch | |justifyCenter | |justifyEnd | |alignCenter | |alignEnd | |alignSelfEnd | |alignSelfStart | |alignSelfCenter | |centered | |positionAbsolute | |positionAbsoluteTopLeft | |positionAbsoluteTopRight | |positionAbsoluteBottomLeft | |positionAbsoluteBottomRight |

Although the styles for colors, borders, spacing and sizes are dynamically generated they are validated using typescript

Creating styled components

Using the useStyleFromProps hook you can easily create component that generate styles from their properties

function MyText(props) {
const style = useStyleFromProps(props);
return <Text style={style}>{children}</Text>
}

<MyText colorWhite bgBlack fontXL marginTop2x>
<MyText colorWhite bgBlack fontXL marginTop="2x">

Any available style from the ThemedStyle service can be used here.

Keep in mind that this is only syntax sugar for the useStyle hook. Defining the styles as a global constant is more optimal for components that are repeated many times (like list items for example)

Extending

The dynamic styles are implemented using a Proxy that creates the required styles on-demand. For this it use generators functions that convert a style name into a react-native style. Currently we have generators for spacing, colors, sizes, and borders.

These generators are located on styles/generators/ folder and they are called from the proxy handler.

Initial design system effort: UI tokens and base UI components

The reasoning

The legacy Mobile UI implementation lacks on centralized UI Development/Design definitions and base UI components. This impacts directly the team's development speed, creating a number of small UI/UX inconsistencies.

As an effort to create a clear abstraction of the commonly used elements between Design and Development, we're creating basic UI Token definitions and a set of pure UI components.

References

Selected articles for contextualization and basic understanding behind the standards that are being adapted on the project.

Spacing and centralized units

For a starting point we're introducing the classic clothing-size model with XXS, XS, S, M, L, XL, XXL together with an optional 2L, 2XL and 2XXL for avoiding extra calculations on increased common sizes.

The reasoning for not using the common pattern of one unit for every dimension (horizontal/vertical) or component type is to avoid an increased number of similar definitions. Having a different unit for horizontal spacing, vertical spacing, fonts, buttons, etc ends up being expensive for maintain and extend. We're using a 4 point base grid, that's centralized on one unit type.

Unit----------
XXXS2
XXS4
XS8
S12
M16
L20
XL24
XXL28

Sizing/type definitions

For creating a base unit abstraction and to improve the ease of communication between teams, we're defining component sizes nomenclature with: micro, tiny, small, medium, large and huge. For the typography model we're using the modified Header1, Header2, Header3, Header4, for the headers and Body1, Body2, Body3, Body4 for body texts.

Other definitions

Color

For reducing the on screen/feature logic, we're prioritizing the flag definition on top of setting the color option manually. Mainly if a component has a small set of well defined color variations we'd use the active light disabled patterns to help clearing up the logic on the outside world.

Default medium size

Based on the most used pattern, where the most used style defines the default component prop and medium values.

Font-sizes

Font-sizes have a value on it's own realm. As fonts snap to the grid through it's line-height, font-sizes uses typed specific constants. While it's line-height keep the usual inheritance from the centralized UNIT.

Wrapped component decision

Following the principle of the most used pattern and trying to respect the atomization of each element, so styles and behaviors that are not commonly used throughout the project are delegated to wrapper components. i.e. on the Icon set we have the IconButton and IconCircle wrappers.

Applying the spacer

The space units can be imported on UI components from the ~styles/tokens.ts file, applied using the Spacer component as a parent or wrapping the component itself with the withSpacer HOC.

Eg:

import {Spacer, withSpacer, Icon} from "~ui"

const Component = (
<Spacer top="XXL" left="L">
/* ... */
</Spacer>
}

const IconS = withSpacer(Icon)
Legacy icon replacement example

On the legacy implementation, we were importing different icon families per screen/feature and defining manually their styling and behavior.

import MIcon from 'react-native-vector-icons/MaterialIcons';
import Icon from 'react-native-vector-icons/MaterialIcons';
...
const chevron = <MIcon
size={34}
name="chevron-left"
style={styles.backIcon}
onPress={props.onPressBack}
/>
...
const error = <Icon name="error" size={32} color="white" />

The new strategy consists on abstracting as much as we can to a base component level to avoid the per screen/feature, strengthening the centralization, accelerating the feature development process and avoiding minor UI bugs and inconsistencies.

import { Icon, IconButton } from "~ui/icons";

const chevron = (
<IconButton name="chevron-left" size="small" onPress={onPressBack} />
);

const error = <Icon name="error" />;

Benchmarking components on-screen

We've created a small on-screen benchmarking tool at ~/performance for analyzing the component's on-screen render performance. It uses React's Profiler and render a defined number (5k/10k/20k) of components measuring it's render time on mount, update (3x) and unmount. With the option of setting up multiple experiments at a time.

Experiment setup example

const experimentA = () => {
return {
id, // experiment id
name, // experiment name
mount: generateIcons, // generate random props for the initial mount
update: generateIcons, // generate random props for the 2x/3x updates
component: () => IconNext, // return the base component
};
};

Spacer, row, and column

These are the inner structural components. They are a shortcut to easily access common Row and Column flex properties. They're useful for replacing the atomic components on the legacy interface.

Replacement example

Checkout the drawer replacement example.

const WITH_BASE = (
<Row left="XL2" right="XL" bottom="XXL">
<Avatar source={avatar} size="medium" onPress={onUserPress} />
<Column flex centerStart horizontal="M">
<H2 onPress={onUserPress} bold>
{name || `@${username}`}
</H2>
{name && (
<B1 flat light onPress={onUserPress}>
@{username}
</B1>
)}
</Column>
<IconButton scale name="account-multi" onPress={onIconPress} />
</Row>
);

Screen, ScreenHeader, ScreenSection

The screen components are aimed to control the outside structure of the screen. Flexing, basic scrolling, safe insets, default background and horizontal spacing.

Storybook

As the component base grows we're implementing an UI Storybook for centralizing the isolated implementation, documentation and visual regression of the library - TBD.