Repository files navigation

@shopify/restyle

RestyleTheme 2020-02-25 17_43_51

The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.

This library assumes that the UI is built upon a design system that (at the very least) defines a set of colors and spacing constants that lays as a foundation. While the library acknowledges that there can be exceptions to the system by allowing any style to be overridden, it keeps the developer most productive when one-off values are kept to a minimum.

Here's an example of how a view built with Restyle components could look:

import{ThemeProvider,createBox,createText,createRestyleComponent,createVariant,VariantProps,}from'@shopify/restyle';// See the "Defining Your Theme" readme section belowimporttheme,{Theme}from'./theme';constBox=createBox<Theme>();constText=createText<Theme>();constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>&React.ComponentProps<typeofBox>>([createVariant({themeKey: 'cardVariants'})],Box);constWelcome=()=>{return(<Boxflex={1}backgroundColor="mainBackground"paddingVertical="xl"paddingHorizontal="m"><Textvariant="header">Welcome</Text><BoxflexDirection={{phone: 'column',tablet: 'row',}}><Cardmargin="s"variant="secondary"><Textvariant="body">This is a simple example</Text></Card><Cardmargin="s"variant="primary"><Textvariant="body">Displaying how to use Restyle</Text></Card></Box></Box>);};constApp=()=>{return(<ThemeProvidertheme={theme}><Welcome/></ThemeProvider>);};

Restyle Component Workflow

Installation

Yarn

$ yarn add @shopify/restyle

NPM

$ npm install @shopify/restyle

Usage

Defining Your Theme

Any project using this library should have a global theme object. It specifies set values for spacing, colors, breakpoints, and more. These values are made available to Restyle components, so that you can for example write backgroundColor="cardPrimary" to use the named color from your theme. In fact, TypeScript enforces the backgroundColor property to only accept colors that have been defined in your theme, and autocompletes values for you in a modern editor.

Below is an example of how a basic theme could look. Make sure to read the sections below for more details on how to set up your different theme values.

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,cardPrimaryBackground: palette.purplePrimary,},spacing: {s: 8,m: 16,l: 24,xl: 40,},breakpoints: {phone: 0,tablet: 768,},};exporttypeTheme=typeoftheme;exportdefaulttheme;

This theme should be passed to a ThemeProvider at the top of your React tree:

import{ThemeProvider}from'@shopify/restyle';importthemefrom'./theme';constApp=()=>(<ThemeProvidertheme={theme}>{/* Rest of the app */}</ThemeProvider>);

Colors

When working with colors in a design system a common pattern is to have a palette including a number of base colors with darker and lighter shades, see for example the Polaris Color Palette.

This palette should preferrably not be directly included as values in the theme. The naming of colors in the theme object should instead be used to assign semantic meaning to the palette, see this example:

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,mainForeground: palette.black,cardPrimaryBackground: palette.purplePrimary,buttonPrimaryBackground: palette.purplePrimary,},};

Taking the time to define these semantic meanings comes with a number of benefits:

  • It's easy to understand where and in what context colors are applied throughout the app
  • If changes are made to the palette (e.g. the purple colors are changed to a shade of blue instead), we only have to update what the semantic names point to instead of updating all references to purplePrimary throughout the app.
  • Even though cardPrimaryBackground and buttonPrimaryBackground point to the same color in the example above, deciding that buttons should instead be green (while cards remain purple) becomes a trivial change.
  • A theme can easily be swapped at runtime.

Spacing

Spacing tends to follow multiples of a given base spacing number, for example 8. We prefer using the t-shirt size naming convention, because of the scalability of it (any number of x's can be prepended for smaller and larger sizes):

consttheme={spacing: {s: 8,m: 16,l: 24,xl: 40,},};

Breakpoints

Breakpoints are defined as minimum widths (inclusive) for different target screen sizes where we want to apply differing styles. Consider giving your breakpoints names that give a general idea of the type of device the user is using:

consttheme={breakpoints: {phone: 0,tablet: 768,largeTablet: 1024,},};

See the Responsive Values section to see how these can be used.

Accessing the Theme

If you need to manually access the theme outside of a component created with Restyle, use the useTheme hook:

constComponent=()=>{consttheme=useTheme<Theme>();const{cardPrimaryBackground}=theme.colors;// ...};

By doing this instead of directly importing the theme object, it becomes easy to swap the theme out during runtime to for example implement a dark mode switch in your app.

Predefined Components

This library comes with predefined functions to create a Box and Text component, as seen in action in the introductory example. These come as functions instead of ready-made components to give you a chance to provide the type of your theme object. Doing this will make all props that map to theme values have proper types configured, based on what's available in your theme.

Box

// In Box.tsximport{createBox}from'@shopify/restyle';import{Theme}from'./theme';constBox=createBox<Theme>();exportdefaultBox;

The Box component comes with the following Restyle functions: backgroundColor, opacity, visible, layout, spacing, border, shadow, position.

Text

// In Text.tsximport{createText}from'@shopify/restyle';import{Theme}from'./theme';constText=createText<Theme>();exportdefaultText;

The Text component comes with the following Restyle functions: color, opacity, visible, typography, textShadow, spacing. It also includes a variant that picks up styles under the textVariants key in your theme:

// In your themeconsttheme={
...,textVariants: {header: {fontFamily: 'ShopifySans-Bold',fontWeight: 'bold',fontSize: 34,lineHeight: 42.5,color: 'black',},subheader: {fontFamily: 'ShopifySans-SemiBold',fontWeight: '600',fontSize: 28,lineHeight: 36,color: 'black',},body: {fontFamily: 'ShopifySans',fontSize: 16,lineHeight: 24,color: 'black',},},}// In a component<Textvariant="header">Header</Text>

Custom Components

If you want to create your own component similar to Box or Text, but decide yourself which Restyle functions to use, use the createRestyleComponent helper:

import{createRestyleComponent,createVariant,spacing,SpacingProps,VariantProps}from'@shopify/restyle';import{Theme}from'./theme'typeProps=SpacingProps<Theme>&VariantProps<Theme,'cardVariants'>constCard=createRestyleComponent<Props>([spacing,createVariant({themeKey: 'cardVariants'})])exportdefaultCard

For more advanced components, you may want to instead use the useRestyle hook:

import{TouchableOpacity,View}from'react-native';import{useRestyle,spacing,border,backgroundColor,SpacingProps,BorderProps,BackgroundColorProps,}from'@shopify/restyle';importTextfrom'./Text';import{Theme}from'./theme';constrestyleFunctions=[spacing,border,backgroundColor];typeProps=SpacingProps<Theme>&BorderProps<Theme>&BackgroundColorProps<Theme>&{onPress: ()=>void;};constButton=({onPress, label, ...rest}: Props)=>{constprops=useRestyle(restyleFunctions,rest);return(<TouchableOpacityonPress={onPress}><View{...props}><Textvariant="buttonLabel">{label}</Text></View></TouchableOpacity>);};

Restyle Functions

Restyle functions are the bread and butter of Restyle. They specify how props should be mapped to values in a resulting style object, that can then be passed down to a React Native component. The props support responsive values and can be mapped to values in your theme.

Predefined Restyle Functions

The Restyle library comes with a number of predefined Restyle functions for your convenience.

Restyle FunctionPropsTheme Key
backgroundColorbackgroundColorcolors
colorcolorcolors
opacityopacitynone
visibledisplay (maps true / false to flex / none)none
spacingmargin, marginTop, marginRight, marginBottom, marginLeft, marginHorizontal, marginVertical, padding, paddingTop, paddingRight, paddingBottom, paddingLeft, paddingHorizontal, paddingVerticalspacing
layoutwidth, height, minWidth, maxWidth, minHeight, maxHeight, overflow, aspectRatio, alignContent, alignItems, alignSelf, justifyContent, flex, flexBasis, flexDirection, flexGrow, flexShrink, flexWrapnone
positionposition, top, right, bottom, leftnone
positionzIndexzIndices
borderborderBottomWidth, borderLeftWidth, borderRightWidth, borderStyle, borderTopWidth, borderWidthnone
borderborderColor, borderTopColor, borderRightColor, borderLeftColor, borderBottomColorcolors
borderborderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadiusborderRadii
shadowshadowOpacity, shadowOffset, shadowRadius, elevationnone
shadowshadowColorcolors
textShadowtextShadowOffset, textShadowRadiusnone
textShadowtextShadowColorcolors
typographyfontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, textAlign, textDecorationLine, textDecorationStyle, textTransformnone

Custom Restyle Functions

To define your own Restyle function, use the createRestyleFunction helper:

import{createRestyleFunction,createRestyleFunction}from'@shopify/restyle'consttransparency=createRestyleFunction({property: 'transparency',styleProperty: 'opacity',transform: ({value}: {value: number})=>1-value,});constTransparentComponent=createRestyleFunction([transparency])<TransparentComponenttransparency={0.5}/>

Arguments:

  • property: The name of the component prop that the function will receive tha value of.
  • styleProperty: The name of the property in the style object to map to. Defaults to the value of property.
  • transform({value, theme, themeKey}): An optional function that transforms the value of the prop to the value that will be inserted into the style object.
  • themeKey: An optional key in the theme to map values from, e.g. colors.

Variants

A variant is a form of Restyle function that maps a prop into multiple other props to use with Restyle functions. A variant needs to always map to a key in the theme.

// In themeconsttheme={// ...spacing: {s: 8,m: 16,l: 24,},colors: {cardRegularBackground: '#EEEEEE',},breakpoints: {phone: 0,tablet: 768,},cardVariants: {regular: {// We can refer to other values in the theme here, and use responsive propspadding: {phone: 's',tablet: 'm',},}elevated: {padding: {phone: 's',tablet: 'm',},shadowColor: '#000',shadowOpacity: 0.2,shadowOffset: {width: 0,height: 5},shadowRadius: 15,elevation: 5,}}}import{createVariant,createRestyleComponent,VariantProps}from'@shopify/restyle'constvariant=createVariant<Theme>({themeKey: 'cardVariants',defaults: {margin: {phone: 's',tablet: 'm',},backgroundColor: 'cardRegularBackground',}})constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>>([variant])<Cardvariant="elevated"/>

Arguments:

  • property: The name of the component prop that will map to a variant. Defaults to variant.
  • themeKey: A key in the theme to map values from. Unlike createRestyleFunction, this option is required to create a variant.
  • defaults: The default values to apply before applying anything from the values in the theme.

Responsive Values

Any prop powered by Restyle can optionally accept a value for each screen size, as defined by the breakpoints object in the theme:

// In your themeconsttheme={// ...breakpoints: {phone: 0,tablet: 768,}}// Props always accept either plain values<BoxflexDirection="row"/>// Or breakpoint-specific values<BoxflexDirection={{phone: 'column',tablet: 'row'}}/>

Overriding Styles

Any Restyle component also accepts a regular style property and will apply it after all other styles, which means that you can use this to do any overrides that you might find necessary.

<Boxmargin="s"padding="m"style={{backgroundColor: '#F00BAA',}}/>

Implementing Dark Mode

Of course, no app is complete without a dark mode. Here a simple example of how you would implement it:

importReact,{useState}from'react';import{Switch}from'react-native';import{ThemeProvider,createBox,createText}from'@shopify/restyle';exportconstpalette={purple: '#5A31F4',white: '#FFF',black: '#111',darkGray: '#333',lightGray: '#EEE',};consttheme={spacing: {s: 8,m: 16,},colors: {mainBackground: palette.lightGray,mainForeground: palette.black,primaryCardBackground: palette.purple,secondaryCardBackground: palette.white,primaryCardText: palette.white,secondaryCardText: palette.black,},breakpoints: {},textVariants: {body: {fontSize: 16,lineHeight: 24,color: 'mainForeground',},},cardVariants: {primary: {backgroundColor: 'primaryCardBackground',shadowOpacity: 0.3,},secondary: {backgroundColor: 'secondaryCardBackground',shadowOpacity: 0.1,},},};typeTheme=typeoftheme;constdarkTheme={
...theme,colors: {
...theme.colors,mainBackground: palette.black,mainForeground: palette.white,secondaryCardBackground: palette.darkGray,secondaryCardText: palette.white,},};constBox=createBox<Theme>();constText=createText<Theme>();constApp=()=>{const[darkMode,setDarkMode]=useState(false);return(<ThemeProvidertheme={darkMode ? darkTheme : theme}><Boxpadding="m"backgroundColor="mainBackground"flex={1}><BoxbackgroundColor="primaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="primaryCardText">
Primary Card
</Text></Box><BoxbackgroundColor="secondaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="secondaryCardText">
Secondary Card
</Text></Box><BoxmarginTop="m"><Switchvalue={darkMode}onValueChange={(value: boolean)=>setDarkMode(value)}/></Box></Box></ThemeProvider>);};exportdefaultApp;

Inspiration

Restyle is heavily inspired by https://styled-system.com.

Contributing

For help on setting up the repo locally, building, testing, and contributing please see CONTRIBUTING.md.

Code of Conduct

All developers who wish to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE.md for details.

About

A type-enforced system for building UI components in React Native with TypeScript.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

@shopify/restyle

RestyleTheme 2020-02-25 17_43_51

The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.

This library assumes that the UI is built upon a design system that (at the very least) defines a set of colors and spacing constants that lays as a foundation. While the library acknowledges that there can be exceptions to the system by allowing any style to be overridden, it keeps the developer most productive when one-off values are kept to a minimum.

Here's an example of how a view built with Restyle components could look:

import{ThemeProvider,createBox,createText,createRestyleComponent,createVariant,VariantProps,}from'@shopify/restyle';// See the "Defining Your Theme" readme section belowimporttheme,{Theme}from'./theme';constBox=createBox<Theme>();constText=createText<Theme>();constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>&React.ComponentProps<typeofBox>>([createVariant({themeKey: 'cardVariants'})],Box);constWelcome=()=>{return(<Boxflex={1}backgroundColor="mainBackground"paddingVertical="xl"paddingHorizontal="m"><Textvariant="header">Welcome</Text><BoxflexDirection={{phone: 'column',tablet: 'row',}}><Cardmargin="s"variant="secondary"><Textvariant="body">This is a simple example</Text></Card><Cardmargin="s"variant="primary"><Textvariant="body">Displaying how to use Restyle</Text></Card></Box></Box>);};constApp=()=>{return(<ThemeProvidertheme={theme}><Welcome/></ThemeProvider>);};

Restyle Component Workflow

Installation

Yarn

$ yarn add @shopify/restyle

NPM

$ npm install @shopify/restyle

Usage

Defining Your Theme

Any project using this library should have a global theme object. It specifies set values for spacing, colors, breakpoints, and more. These values are made available to Restyle components, so that you can for example write backgroundColor="cardPrimary" to use the named color from your theme. In fact, TypeScript enforces the backgroundColor property to only accept colors that have been defined in your theme, and autocompletes values for you in a modern editor.

Below is an example of how a basic theme could look. Make sure to read the sections below for more details on how to set up your different theme values.

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,cardPrimaryBackground: palette.purplePrimary,},spacing: {s: 8,m: 16,l: 24,xl: 40,},breakpoints: {phone: 0,tablet: 768,},};exporttypeTheme=typeoftheme;exportdefaulttheme;

This theme should be passed to a ThemeProvider at the top of your React tree:

import{ThemeProvider}from'@shopify/restyle';importthemefrom'./theme';constApp=()=>(<ThemeProvidertheme={theme}>{/* Rest of the app */}</ThemeProvider>);

Colors

When working with colors in a design system a common pattern is to have a palette including a number of base colors with darker and lighter shades, see for example the Polaris Color Palette.

This palette should preferrably not be directly included as values in the theme. The naming of colors in the theme object should instead be used to assign semantic meaning to the palette, see this example:

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,mainForeground: palette.black,cardPrimaryBackground: palette.purplePrimary,buttonPrimaryBackground: palette.purplePrimary,},};

Taking the time to define these semantic meanings comes with a number of benefits:

  • It's easy to understand where and in what context colors are applied throughout the app
  • If changes are made to the palette (e.g. the purple colors are changed to a shade of blue instead), we only have to update what the semantic names point to instead of updating all references to purplePrimary throughout the app.
  • Even though cardPrimaryBackground and buttonPrimaryBackground point to the same color in the example above, deciding that buttons should instead be green (while cards remain purple) becomes a trivial change.
  • A theme can easily be swapped at runtime.

Spacing

Spacing tends to follow multiples of a given base spacing number, for example 8. We prefer using the t-shirt size naming convention, because of the scalability of it (any number of x's can be prepended for smaller and larger sizes):

consttheme={spacing: {s: 8,m: 16,l: 24,xl: 40,},};

Breakpoints

Breakpoints are defined as minimum widths (inclusive) for different target screen sizes where we want to apply differing styles. Consider giving your breakpoints names that give a general idea of the type of device the user is using:

consttheme={breakpoints: {phone: 0,tablet: 768,largeTablet: 1024,},};

See the Responsive Values section to see how these can be used.

Accessing the Theme

If you need to manually access the theme outside of a component created with Restyle, use the useTheme hook:

constComponent=()=>{consttheme=useTheme<Theme>();const{cardPrimaryBackground}=theme.colors;// ...};

By doing this instead of directly importing the theme object, it becomes easy to swap the theme out during runtime to for example implement a dark mode switch in your app.

Predefined Components

This library comes with predefined functions to create a Box and Text component, as seen in action in the introductory example. These come as functions instead of ready-made components to give you a chance to provide the type of your theme object. Doing this will make all props that map to theme values have proper types configured, based on what's available in your theme.

Box

// In Box.tsximport{createBox}from'@shopify/restyle';import{Theme}from'./theme';constBox=createBox<Theme>();exportdefaultBox;

The Box component comes with the following Restyle functions: backgroundColor, opacity, visible, layout, spacing, border, shadow, position.

Text

// In Text.tsximport{createText}from'@shopify/restyle';import{Theme}from'./theme';constText=createText<Theme>();exportdefaultText;

The Text component comes with the following Restyle functions: color, opacity, visible, typography, textShadow, spacing. It also includes a variant that picks up styles under the textVariants key in your theme:

// In your themeconsttheme={
...,textVariants: {header: {fontFamily: 'ShopifySans-Bold',fontWeight: 'bold',fontSize: 34,lineHeight: 42.5,color: 'black',},subheader: {fontFamily: 'ShopifySans-SemiBold',fontWeight: '600',fontSize: 28,lineHeight: 36,color: 'black',},body: {fontFamily: 'ShopifySans',fontSize: 16,lineHeight: 24,color: 'black',},},}// In a component<Textvariant="header">Header</Text>

Custom Components

If you want to create your own component similar to Box or Text, but decide yourself which Restyle functions to use, use the createRestyleComponent helper:

import{createRestyleComponent,createVariant,spacing,SpacingProps,VariantProps}from'@shopify/restyle';import{Theme}from'./theme'typeProps=SpacingProps<Theme>&VariantProps<Theme,'cardVariants'>constCard=createRestyleComponent<Props>([spacing,createVariant({themeKey: 'cardVariants'})])exportdefaultCard

For more advanced components, you may want to instead use the useRestyle hook:

import{TouchableOpacity,View}from'react-native';import{useRestyle,spacing,border,backgroundColor,SpacingProps,BorderProps,BackgroundColorProps,}from'@shopify/restyle';importTextfrom'./Text';import{Theme}from'./theme';constrestyleFunctions=[spacing,border,backgroundColor];typeProps=SpacingProps<Theme>&BorderProps<Theme>&BackgroundColorProps<Theme>&{onPress: ()=>void;};constButton=({onPress, label, ...rest}: Props)=>{constprops=useRestyle(restyleFunctions,rest);return(<TouchableOpacityonPress={onPress}><View{...props}><Textvariant="buttonLabel">{label}</Text></View></TouchableOpacity>);};

Restyle Functions

Restyle functions are the bread and butter of Restyle. They specify how props should be mapped to values in a resulting style object, that can then be passed down to a React Native component. The props support responsive values and can be mapped to values in your theme.

Predefined Restyle Functions

The Restyle library comes with a number of predefined Restyle functions for your convenience.

Restyle FunctionPropsTheme Key
backgroundColorbackgroundColorcolors
colorcolorcolors
opacityopacitynone
visibledisplay (maps true / false to flex / none)none
spacingmargin, marginTop, marginRight, marginBottom, marginLeft, marginHorizontal, marginVertical, padding, paddingTop, paddingRight, paddingBottom, paddingLeft, paddingHorizontal, paddingVerticalspacing
layoutwidth, height, minWidth, maxWidth, minHeight, maxHeight, overflow, aspectRatio, alignContent, alignItems, alignSelf, justifyContent, flex, flexBasis, flexDirection, flexGrow, flexShrink, flexWrapnone
positionposition, top, right, bottom, leftnone
positionzIndexzIndices
borderborderBottomWidth, borderLeftWidth, borderRightWidth, borderStyle, borderTopWidth, borderWidthnone
borderborderColor, borderTopColor, borderRightColor, borderLeftColor, borderBottomColorcolors
borderborderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadiusborderRadii
shadowshadowOpacity, shadowOffset, shadowRadius, elevationnone
shadowshadowColorcolors
textShadowtextShadowOffset, textShadowRadiusnone
textShadowtextShadowColorcolors
typographyfontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, textAlign, textDecorationLine, textDecorationStyle, textTransformnone

Custom Restyle Functions

To define your own Restyle function, use the createRestyleFunction helper:

import{createRestyleFunction,createRestyleFunction}from'@shopify/restyle'consttransparency=createRestyleFunction({property: 'transparency',styleProperty: 'opacity',transform: ({value}: {value: number})=>1-value,});constTransparentComponent=createRestyleFunction([transparency])<TransparentComponenttransparency={0.5}/>

Arguments:

  • property: The name of the component prop that the function will receive tha value of.
  • styleProperty: The name of the property in the style object to map to. Defaults to the value of property.
  • transform({value, theme, themeKey}): An optional function that transforms the value of the prop to the value that will be inserted into the style object.
  • themeKey: An optional key in the theme to map values from, e.g. colors.

Variants

A variant is a form of Restyle function that maps a prop into multiple other props to use with Restyle functions. A variant needs to always map to a key in the theme.

// In themeconsttheme={// ...spacing: {s: 8,m: 16,l: 24,},colors: {cardRegularBackground: '#EEEEEE',},breakpoints: {phone: 0,tablet: 768,},cardVariants: {regular: {// We can refer to other values in the theme here, and use responsive propspadding: {phone: 's',tablet: 'm',},}elevated: {padding: {phone: 's',tablet: 'm',},shadowColor: '#000',shadowOpacity: 0.2,shadowOffset: {width: 0,height: 5},shadowRadius: 15,elevation: 5,}}}import{createVariant,createRestyleComponent,VariantProps}from'@shopify/restyle'constvariant=createVariant<Theme>({themeKey: 'cardVariants',defaults: {margin: {phone: 's',tablet: 'm',},backgroundColor: 'cardRegularBackground',}})constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>>([variant])<Cardvariant="elevated"/>

Arguments:

  • property: The name of the component prop that will map to a variant. Defaults to variant.
  • themeKey: A key in the theme to map values from. Unlike createRestyleFunction, this option is required to create a variant.
  • defaults: The default values to apply before applying anything from the values in the theme.

Responsive Values

Any prop powered by Restyle can optionally accept a value for each screen size, as defined by the breakpoints object in the theme:

// In your themeconsttheme={// ...breakpoints: {phone: 0,tablet: 768,}}// Props always accept either plain values<BoxflexDirection="row"/>// Or breakpoint-specific values<BoxflexDirection={{phone: 'column',tablet: 'row'}}/>

Overriding Styles

Any Restyle component also accepts a regular style property and will apply it after all other styles, which means that you can use this to do any overrides that you might find necessary.

<Boxmargin="s"padding="m"style={{backgroundColor: '#F00BAA',}}/>

Implementing Dark Mode

Of course, no app is complete without a dark mode. Here a simple example of how you would implement it:

importReact,{useState}from'react';import{Switch}from'react-native';import{ThemeProvider,createBox,createText}from'@shopify/restyle';exportconstpalette={purple: '#5A31F4',white: '#FFF',black: '#111',darkGray: '#333',lightGray: '#EEE',};consttheme={spacing: {s: 8,m: 16,},colors: {mainBackground: palette.lightGray,mainForeground: palette.black,primaryCardBackground: palette.purple,secondaryCardBackground: palette.white,primaryCardText: palette.white,secondaryCardText: palette.black,},breakpoints: {},textVariants: {body: {fontSize: 16,lineHeight: 24,color: 'mainForeground',},},cardVariants: {primary: {backgroundColor: 'primaryCardBackground',shadowOpacity: 0.3,},secondary: {backgroundColor: 'secondaryCardBackground',shadowOpacity: 0.1,},},};typeTheme=typeoftheme;constdarkTheme={
...theme,colors: {
...theme.colors,mainBackground: palette.black,mainForeground: palette.white,secondaryCardBackground: palette.darkGray,secondaryCardText: palette.white,},};constBox=createBox<Theme>();constText=createText<Theme>();constApp=()=>{const[darkMode,setDarkMode]=useState(false);return(<ThemeProvidertheme={darkMode ? darkTheme : theme}><Boxpadding="m"backgroundColor="mainBackground"flex={1}><BoxbackgroundColor="primaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="primaryCardText">
Primary Card
</Text></Box><BoxbackgroundColor="secondaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="secondaryCardText">
Secondary Card
</Text></Box><BoxmarginTop="m"><Switchvalue={darkMode}onValueChange={(value: boolean)=>setDarkMode(value)}/></Box></Box></ThemeProvider>);};exportdefaultApp;

Inspiration

Restyle is heavily inspired by https://styled-system.com.

Contributing

For help on setting up the repo locally, building, testing, and contributing please see CONTRIBUTING.md.

Code of Conduct

All developers who wish to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE.md for details.

About

A type-enforced system for building UI components in React Native with TypeScript.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@shopify/restyle

RestyleTheme 2020-02-25 17_43_51

The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.

This library assumes that the UI is built upon a design system that (at the very least) defines a set of colors and spacing constants that lays as a foundation. While the library acknowledges that there can be exceptions to the system by allowing any style to be overridden, it keeps the developer most productive when one-off values are kept to a minimum.

Here's an example of how a view built with Restyle components could look:

import{ThemeProvider,createBox,createText,createRestyleComponent,createVariant,VariantProps,}from'@shopify/restyle';// See the "Defining Your Theme" readme section belowimporttheme,{Theme}from'./theme';constBox=createBox<Theme>();constText=createText<Theme>();constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>&React.ComponentProps<typeofBox>>([createVariant({themeKey: 'cardVariants'})],Box);constWelcome=()=>{return(<Boxflex={1}backgroundColor="mainBackground"paddingVertical="xl"paddingHorizontal="m"><Textvariant="header">Welcome</Text><BoxflexDirection={{phone: 'column',tablet: 'row',}}><Cardmargin="s"variant="secondary"><Textvariant="body">This is a simple example</Text></Card><Cardmargin="s"variant="primary"><Textvariant="body">Displaying how to use Restyle</Text></Card></Box></Box>);};constApp=()=>{return(<ThemeProvidertheme={theme}><Welcome/></ThemeProvider>);};

Restyle Component Workflow

Installation

Yarn

$ yarn add @shopify/restyle

NPM

$ npm install @shopify/restyle

Usage

Defining Your Theme

Any project using this library should have a global theme object. It specifies set values for spacing, colors, breakpoints, and more. These values are made available to Restyle components, so that you can for example write backgroundColor="cardPrimary" to use the named color from your theme. In fact, TypeScript enforces the backgroundColor property to only accept colors that have been defined in your theme, and autocompletes values for you in a modern editor.

Below is an example of how a basic theme could look. Make sure to read the sections below for more details on how to set up your different theme values.

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,cardPrimaryBackground: palette.purplePrimary,},spacing: {s: 8,m: 16,l: 24,xl: 40,},breakpoints: {phone: 0,tablet: 768,},};exporttypeTheme=typeoftheme;exportdefaulttheme;

This theme should be passed to a ThemeProvider at the top of your React tree:

import{ThemeProvider}from'@shopify/restyle';importthemefrom'./theme';constApp=()=>(<ThemeProvidertheme={theme}>{/* Rest of the app */}</ThemeProvider>);

Colors

When working with colors in a design system a common pattern is to have a palette including a number of base colors with darker and lighter shades, see for example the Polaris Color Palette.

This palette should preferrably not be directly included as values in the theme. The naming of colors in the theme object should instead be used to assign semantic meaning to the palette, see this example:

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,mainForeground: palette.black,cardPrimaryBackground: palette.purplePrimary,buttonPrimaryBackground: palette.purplePrimary,},};

Taking the time to define these semantic meanings comes with a number of benefits:

  • It's easy to understand where and in what context colors are applied throughout the app
  • If changes are made to the palette (e.g. the purple colors are changed to a shade of blue instead), we only have to update what the semantic names point to instead of updating all references to purplePrimary throughout the app.
  • Even though cardPrimaryBackground and buttonPrimaryBackground point to the same color in the example above, deciding that buttons should instead be green (while cards remain purple) becomes a trivial change.
  • A theme can easily be swapped at runtime.

Spacing

Spacing tends to follow multiples of a given base spacing number, for example 8. We prefer using the t-shirt size naming convention, because of the scalability of it (any number of x's can be prepended for smaller and larger sizes):

consttheme={spacing: {s: 8,m: 16,l: 24,xl: 40,},};

Breakpoints

Breakpoints are defined as minimum widths (inclusive) for different target screen sizes where we want to apply differing styles. Consider giving your breakpoints names that give a general idea of the type of device the user is using:

consttheme={breakpoints: {phone: 0,tablet: 768,largeTablet: 1024,},};

See the Responsive Values section to see how these can be used.

Accessing the Theme

If you need to manually access the theme outside of a component created with Restyle, use the useTheme hook:

constComponent=()=>{consttheme=useTheme<Theme>();const{cardPrimaryBackground}=theme.colors;// ...};

By doing this instead of directly importing the theme object, it becomes easy to swap the theme out during runtime to for example implement a dark mode switch in your app.

Predefined Components

This library comes with predefined functions to create a Box and Text component, as seen in action in the introductory example. These come as functions instead of ready-made components to give you a chance to provide the type of your theme object. Doing this will make all props that map to theme values have proper types configured, based on what's available in your theme.

Box

// In Box.tsximport{createBox}from'@shopify/restyle';import{Theme}from'./theme';constBox=createBox<Theme>();exportdefaultBox;

The Box component comes with the following Restyle functions: backgroundColor, opacity, visible, layout, spacing, border, shadow, position.

Text

// In Text.tsximport{createText}from'@shopify/restyle';import{Theme}from'./theme';constText=createText<Theme>();exportdefaultText;

The Text component comes with the following Restyle functions: color, opacity, visible, typography, textShadow, spacing. It also includes a variant that picks up styles under the textVariants key in your theme:

// In your themeconsttheme={
...,textVariants: {header: {fontFamily: 'ShopifySans-Bold',fontWeight: 'bold',fontSize: 34,lineHeight: 42.5,color: 'black',},subheader: {fontFamily: 'ShopifySans-SemiBold',fontWeight: '600',fontSize: 28,lineHeight: 36,color: 'black',},body: {fontFamily: 'ShopifySans',fontSize: 16,lineHeight: 24,color: 'black',},},}// In a component<Textvariant="header">Header</Text>

Custom Components

If you want to create your own component similar to Box or Text, but decide yourself which Restyle functions to use, use the createRestyleComponent helper:

import{createRestyleComponent,createVariant,spacing,SpacingProps,VariantProps}from'@shopify/restyle';import{Theme}from'./theme'typeProps=SpacingProps<Theme>&VariantProps<Theme,'cardVariants'>constCard=createRestyleComponent<Props>([spacing,createVariant({themeKey: 'cardVariants'})])exportdefaultCard

For more advanced components, you may want to instead use the useRestyle hook:

import{TouchableOpacity,View}from'react-native';import{useRestyle,spacing,border,backgroundColor,SpacingProps,BorderProps,BackgroundColorProps,}from'@shopify/restyle';importTextfrom'./Text';import{Theme}from'./theme';constrestyleFunctions=[spacing,border,backgroundColor];typeProps=SpacingProps<Theme>&BorderProps<Theme>&BackgroundColorProps<Theme>&{onPress: ()=>void;};constButton=({onPress, label, ...rest}: Props)=>{constprops=useRestyle(restyleFunctions,rest);return(<TouchableOpacityonPress={onPress}><View{...props}><Textvariant="buttonLabel">{label}</Text></View></TouchableOpacity>);};

Restyle Functions

Restyle functions are the bread and butter of Restyle. They specify how props should be mapped to values in a resulting style object, that can then be passed down to a React Native component. The props support responsive values and can be mapped to values in your theme.

Predefined Restyle Functions

The Restyle library comes with a number of predefined Restyle functions for your convenience.

Restyle FunctionPropsTheme Key
backgroundColorbackgroundColorcolors
colorcolorcolors
opacityopacitynone
visibledisplay (maps true / false to flex / none)none
spacingmargin, marginTop, marginRight, marginBottom, marginLeft, marginHorizontal, marginVertical, padding, paddingTop, paddingRight, paddingBottom, paddingLeft, paddingHorizontal, paddingVerticalspacing
layoutwidth, height, minWidth, maxWidth, minHeight, maxHeight, overflow, aspectRatio, alignContent, alignItems, alignSelf, justifyContent, flex, flexBasis, flexDirection, flexGrow, flexShrink, flexWrapnone
positionposition, top, right, bottom, leftnone
positionzIndexzIndices
borderborderBottomWidth, borderLeftWidth, borderRightWidth, borderStyle, borderTopWidth, borderWidthnone
borderborderColor, borderTopColor, borderRightColor, borderLeftColor, borderBottomColorcolors
borderborderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadiusborderRadii
shadowshadowOpacity, shadowOffset, shadowRadius, elevationnone
shadowshadowColorcolors
textShadowtextShadowOffset, textShadowRadiusnone
textShadowtextShadowColorcolors
typographyfontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, textAlign, textDecorationLine, textDecorationStyle, textTransformnone

Custom Restyle Functions

To define your own Restyle function, use the createRestyleFunction helper:

import{createRestyleFunction,createRestyleFunction}from'@shopify/restyle'consttransparency=createRestyleFunction({property: 'transparency',styleProperty: 'opacity',transform: ({value}: {value: number})=>1-value,});constTransparentComponent=createRestyleFunction([transparency])<TransparentComponenttransparency={0.5}/>

Arguments:

  • property: The name of the component prop that the function will receive tha value of.
  • styleProperty: The name of the property in the style object to map to. Defaults to the value of property.
  • transform({value, theme, themeKey}): An optional function that transforms the value of the prop to the value that will be inserted into the style object.
  • themeKey: An optional key in the theme to map values from, e.g. colors.

Variants

A variant is a form of Restyle function that maps a prop into multiple other props to use with Restyle functions. A variant needs to always map to a key in the theme.

// In themeconsttheme={// ...spacing: {s: 8,m: 16,l: 24,},colors: {cardRegularBackground: '#EEEEEE',},breakpoints: {phone: 0,tablet: 768,},cardVariants: {regular: {// We can refer to other values in the theme here, and use responsive propspadding: {phone: 's',tablet: 'm',},}elevated: {padding: {phone: 's',tablet: 'm',},shadowColor: '#000',shadowOpacity: 0.2,shadowOffset: {width: 0,height: 5},shadowRadius: 15,elevation: 5,}}}import{createVariant,createRestyleComponent,VariantProps}from'@shopify/restyle'constvariant=createVariant<Theme>({themeKey: 'cardVariants',defaults: {margin: {phone: 's',tablet: 'm',},backgroundColor: 'cardRegularBackground',}})constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>>([variant])<Cardvariant="elevated"/>

Arguments:

  • property: The name of the component prop that will map to a variant. Defaults to variant.
  • themeKey: A key in the theme to map values from. Unlike createRestyleFunction, this option is required to create a variant.
  • defaults: The default values to apply before applying anything from the values in the theme.

Responsive Values

Any prop powered by Restyle can optionally accept a value for each screen size, as defined by the breakpoints object in the theme:

// In your themeconsttheme={// ...breakpoints: {phone: 0,tablet: 768,}}// Props always accept either plain values<BoxflexDirection="row"/>// Or breakpoint-specific values<BoxflexDirection={{phone: 'column',tablet: 'row'}}/>

Overriding Styles

Any Restyle component also accepts a regular style property and will apply it after all other styles, which means that you can use this to do any overrides that you might find necessary.

<Boxmargin="s"padding="m"style={{backgroundColor: '#F00BAA',}}/>

Implementing Dark Mode

Of course, no app is complete without a dark mode. Here a simple example of how you would implement it:

importReact,{useState}from'react';import{Switch}from'react-native';import{ThemeProvider,createBox,createText}from'@shopify/restyle';exportconstpalette={purple: '#5A31F4',white: '#FFF',black: '#111',darkGray: '#333',lightGray: '#EEE',};consttheme={spacing: {s: 8,m: 16,},colors: {mainBackground: palette.lightGray,mainForeground: palette.black,primaryCardBackground: palette.purple,secondaryCardBackground: palette.white,primaryCardText: palette.white,secondaryCardText: palette.black,},breakpoints: {},textVariants: {body: {fontSize: 16,lineHeight: 24,color: 'mainForeground',},},cardVariants: {primary: {backgroundColor: 'primaryCardBackground',shadowOpacity: 0.3,},secondary: {backgroundColor: 'secondaryCardBackground',shadowOpacity: 0.1,},},};typeTheme=typeoftheme;constdarkTheme={
...theme,colors: {
...theme.colors,mainBackground: palette.black,mainForeground: palette.white,secondaryCardBackground: palette.darkGray,secondaryCardText: palette.white,},};constBox=createBox<Theme>();constText=createText<Theme>();constApp=()=>{const[darkMode,setDarkMode]=useState(false);return(<ThemeProvidertheme={darkMode ? darkTheme : theme}><Boxpadding="m"backgroundColor="mainBackground"flex={1}><BoxbackgroundColor="primaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="primaryCardText">
Primary Card
</Text></Box><BoxbackgroundColor="secondaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="secondaryCardText">
Secondary Card
</Text></Box><BoxmarginTop="m"><Switchvalue={darkMode}onValueChange={(value: boolean)=>setDarkMode(value)}/></Box></Box></ThemeProvider>);};exportdefaultApp;

Inspiration

Restyle is heavily inspired by https://styled-system.com.

Contributing

For help on setting up the repo locally, building, testing, and contributing please see CONTRIBUTING.md.

Code of Conduct

All developers who wish to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE.md for details.

About

A type-enforced system for building UI components in React Native with TypeScript.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@shopify/restyle

RestyleTheme 2020-02-25 17_43_51

The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.

This library assumes that the UI is built upon a design system that (at the very least) defines a set of colors and spacing constants that lays as a foundation. While the library acknowledges that there can be exceptions to the system by allowing any style to be overridden, it keeps the developer most productive when one-off values are kept to a minimum.

Here's an example of how a view built with Restyle components could look:

import{ThemeProvider,createBox,createText,createRestyleComponent,createVariant,VariantProps,}from'@shopify/restyle';// See the "Defining Your Theme" readme section belowimporttheme,{Theme}from'./theme';constBox=createBox<Theme>();constText=createText<Theme>();constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>&React.ComponentProps<typeofBox>>([createVariant({themeKey: 'cardVariants'})],Box);constWelcome=()=>{return(<Boxflex={1}backgroundColor="mainBackground"paddingVertical="xl"paddingHorizontal="m"><Textvariant="header">Welcome</Text><BoxflexDirection={{phone: 'column',tablet: 'row',}}><Cardmargin="s"variant="secondary"><Textvariant="body">This is a simple example</Text></Card><Cardmargin="s"variant="primary"><Textvariant="body">Displaying how to use Restyle</Text></Card></Box></Box>);};constApp=()=>{return(<ThemeProvidertheme={theme}><Welcome/></ThemeProvider>);};

Restyle Component Workflow

Installation

Yarn

$ yarn add @shopify/restyle

NPM

$ npm install @shopify/restyle

Usage

Defining Your Theme

Any project using this library should have a global theme object. It specifies set values for spacing, colors, breakpoints, and more. These values are made available to Restyle components, so that you can for example write backgroundColor="cardPrimary" to use the named color from your theme. In fact, TypeScript enforces the backgroundColor property to only accept colors that have been defined in your theme, and autocompletes values for you in a modern editor.

Below is an example of how a basic theme could look. Make sure to read the sections below for more details on how to set up your different theme values.

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,cardPrimaryBackground: palette.purplePrimary,},spacing: {s: 8,m: 16,l: 24,xl: 40,},breakpoints: {phone: 0,tablet: 768,},};exporttypeTheme=typeoftheme;exportdefaulttheme;

This theme should be passed to a ThemeProvider at the top of your React tree:

import{ThemeProvider}from'@shopify/restyle';importthemefrom'./theme';constApp=()=>(<ThemeProvidertheme={theme}>{/* Rest of the app */}</ThemeProvider>);

Colors

When working with colors in a design system a common pattern is to have a palette including a number of base colors with darker and lighter shades, see for example the Polaris Color Palette.

This palette should preferrably not be directly included as values in the theme. The naming of colors in the theme object should instead be used to assign semantic meaning to the palette, see this example:

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,mainForeground: palette.black,cardPrimaryBackground: palette.purplePrimary,buttonPrimaryBackground: palette.purplePrimary,},};

Taking the time to define these semantic meanings comes with a number of benefits:

  • It's easy to understand where and in what context colors are applied throughout the app
  • If changes are made to the palette (e.g. the purple colors are changed to a shade of blue instead), we only have to update what the semantic names point to instead of updating all references to purplePrimary throughout the app.
  • Even though cardPrimaryBackground and buttonPrimaryBackground point to the same color in the example above, deciding that buttons should instead be green (while cards remain purple) becomes a trivial change.
  • A theme can easily be swapped at runtime.

Spacing

Spacing tends to follow multiples of a given base spacing number, for example 8. We prefer using the t-shirt size naming convention, because of the scalability of it (any number of x's can be prepended for smaller and larger sizes):

consttheme={spacing: {s: 8,m: 16,l: 24,xl: 40,},};

Breakpoints

Breakpoints are defined as minimum widths (inclusive) for different target screen sizes where we want to apply differing styles. Consider giving your breakpoints names that give a general idea of the type of device the user is using:

consttheme={breakpoints: {phone: 0,tablet: 768,largeTablet: 1024,},};

See the Responsive Values section to see how these can be used.

Accessing the Theme

If you need to manually access the theme outside of a component created with Restyle, use the useTheme hook:

constComponent=()=>{consttheme=useTheme<Theme>();const{cardPrimaryBackground}=theme.colors;// ...};

By doing this instead of directly importing the theme object, it becomes easy to swap the theme out during runtime to for example implement a dark mode switch in your app.

Predefined Components

This library comes with predefined functions to create a Box and Text component, as seen in action in the introductory example. These come as functions instead of ready-made components to give you a chance to provide the type of your theme object. Doing this will make all props that map to theme values have proper types configured, based on what's available in your theme.

Box

// In Box.tsximport{createBox}from'@shopify/restyle';import{Theme}from'./theme';constBox=createBox<Theme>();exportdefaultBox;

The Box component comes with the following Restyle functions: backgroundColor, opacity, visible, layout, spacing, border, shadow, position.

Text

// In Text.tsximport{createText}from'@shopify/restyle';import{Theme}from'./theme';constText=createText<Theme>();exportdefaultText;

The Text component comes with the following Restyle functions: color, opacity, visible, typography, textShadow, spacing. It also includes a variant that picks up styles under the textVariants key in your theme:

// In your themeconsttheme={
...,textVariants: {header: {fontFamily: 'ShopifySans-Bold',fontWeight: 'bold',fontSize: 34,lineHeight: 42.5,color: 'black',},subheader: {fontFamily: 'ShopifySans-SemiBold',fontWeight: '600',fontSize: 28,lineHeight: 36,color: 'black',},body: {fontFamily: 'ShopifySans',fontSize: 16,lineHeight: 24,color: 'black',},},}// In a component<Textvariant="header">Header</Text>

Custom Components

If you want to create your own component similar to Box or Text, but decide yourself which Restyle functions to use, use the createRestyleComponent helper:

import{createRestyleComponent,createVariant,spacing,SpacingProps,VariantProps}from'@shopify/restyle';import{Theme}from'./theme'typeProps=SpacingProps<Theme>&VariantProps<Theme,'cardVariants'>constCard=createRestyleComponent<Props>([spacing,createVariant({themeKey: 'cardVariants'})])exportdefaultCard

For more advanced components, you may want to instead use the useRestyle hook:

import{TouchableOpacity,View}from'react-native';import{useRestyle,spacing,border,backgroundColor,SpacingProps,BorderProps,BackgroundColorProps,}from'@shopify/restyle';importTextfrom'./Text';import{Theme}from'./theme';constrestyleFunctions=[spacing,border,backgroundColor];typeProps=SpacingProps<Theme>&BorderProps<Theme>&BackgroundColorProps<Theme>&{onPress: ()=>void;};constButton=({onPress, label, ...rest}: Props)=>{constprops=useRestyle(restyleFunctions,rest);return(<TouchableOpacityonPress={onPress}><View{...props}><Textvariant="buttonLabel">{label}</Text></View></TouchableOpacity>);};

Restyle Functions

Restyle functions are the bread and butter of Restyle. They specify how props should be mapped to values in a resulting style object, that can then be passed down to a React Native component. The props support responsive values and can be mapped to values in your theme.

Predefined Restyle Functions

The Restyle library comes with a number of predefined Restyle functions for your convenience.

Restyle FunctionPropsTheme Key
backgroundColorbackgroundColorcolors
colorcolorcolors
opacityopacitynone
visibledisplay (maps true / false to flex / none)none
spacingmargin, marginTop, marginRight, marginBottom, marginLeft, marginHorizontal, marginVertical, padding, paddingTop, paddingRight, paddingBottom, paddingLeft, paddingHorizontal, paddingVerticalspacing
layoutwidth, height, minWidth, maxWidth, minHeight, maxHeight, overflow, aspectRatio, alignContent, alignItems, alignSelf, justifyContent, flex, flexBasis, flexDirection, flexGrow, flexShrink, flexWrapnone
positionposition, top, right, bottom, leftnone
positionzIndexzIndices
borderborderBottomWidth, borderLeftWidth, borderRightWidth, borderStyle, borderTopWidth, borderWidthnone
borderborderColor, borderTopColor, borderRightColor, borderLeftColor, borderBottomColorcolors
borderborderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadiusborderRadii
shadowshadowOpacity, shadowOffset, shadowRadius, elevationnone
shadowshadowColorcolors
textShadowtextShadowOffset, textShadowRadiusnone
textShadowtextShadowColorcolors
typographyfontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, textAlign, textDecorationLine, textDecorationStyle, textTransformnone

Custom Restyle Functions

To define your own Restyle function, use the createRestyleFunction helper:

import{createRestyleFunction,createRestyleFunction}from'@shopify/restyle'consttransparency=createRestyleFunction({property: 'transparency',styleProperty: 'opacity',transform: ({value}: {value: number})=>1-value,});constTransparentComponent=createRestyleFunction([transparency])<TransparentComponenttransparency={0.5}/>

Arguments:

  • property: The name of the component prop that the function will receive tha value of.
  • styleProperty: The name of the property in the style object to map to. Defaults to the value of property.
  • transform({value, theme, themeKey}): An optional function that transforms the value of the prop to the value that will be inserted into the style object.
  • themeKey: An optional key in the theme to map values from, e.g. colors.

Variants

A variant is a form of Restyle function that maps a prop into multiple other props to use with Restyle functions. A variant needs to always map to a key in the theme.

// In themeconsttheme={// ...spacing: {s: 8,m: 16,l: 24,},colors: {cardRegularBackground: '#EEEEEE',},breakpoints: {phone: 0,tablet: 768,},cardVariants: {regular: {// We can refer to other values in the theme here, and use responsive propspadding: {phone: 's',tablet: 'm',},}elevated: {padding: {phone: 's',tablet: 'm',},shadowColor: '#000',shadowOpacity: 0.2,shadowOffset: {width: 0,height: 5},shadowRadius: 15,elevation: 5,}}}import{createVariant,createRestyleComponent,VariantProps}from'@shopify/restyle'constvariant=createVariant<Theme>({themeKey: 'cardVariants',defaults: {margin: {phone: 's',tablet: 'm',},backgroundColor: 'cardRegularBackground',}})constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>>([variant])<Cardvariant="elevated"/>

Arguments:

  • property: The name of the component prop that will map to a variant. Defaults to variant.
  • themeKey: A key in the theme to map values from. Unlike createRestyleFunction, this option is required to create a variant.
  • defaults: The default values to apply before applying anything from the values in the theme.

Responsive Values

Any prop powered by Restyle can optionally accept a value for each screen size, as defined by the breakpoints object in the theme:

// In your themeconsttheme={// ...breakpoints: {phone: 0,tablet: 768,}}// Props always accept either plain values<BoxflexDirection="row"/>// Or breakpoint-specific values<BoxflexDirection={{phone: 'column',tablet: 'row'}}/>

Overriding Styles

Any Restyle component also accepts a regular style property and will apply it after all other styles, which means that you can use this to do any overrides that you might find necessary.

<Boxmargin="s"padding="m"style={{backgroundColor: '#F00BAA',}}/>

Implementing Dark Mode

Of course, no app is complete without a dark mode. Here a simple example of how you would implement it:

importReact,{useState}from'react';import{Switch}from'react-native';import{ThemeProvider,createBox,createText}from'@shopify/restyle';exportconstpalette={purple: '#5A31F4',white: '#FFF',black: '#111',darkGray: '#333',lightGray: '#EEE',};consttheme={spacing: {s: 8,m: 16,},colors: {mainBackground: palette.lightGray,mainForeground: palette.black,primaryCardBackground: palette.purple,secondaryCardBackground: palette.white,primaryCardText: palette.white,secondaryCardText: palette.black,},breakpoints: {},textVariants: {body: {fontSize: 16,lineHeight: 24,color: 'mainForeground',},},cardVariants: {primary: {backgroundColor: 'primaryCardBackground',shadowOpacity: 0.3,},secondary: {backgroundColor: 'secondaryCardBackground',shadowOpacity: 0.1,},},};typeTheme=typeoftheme;constdarkTheme={
...theme,colors: {
...theme.colors,mainBackground: palette.black,mainForeground: palette.white,secondaryCardBackground: palette.darkGray,secondaryCardText: palette.white,},};constBox=createBox<Theme>();constText=createText<Theme>();constApp=()=>{const[darkMode,setDarkMode]=useState(false);return(<ThemeProvidertheme={darkMode ? darkTheme : theme}><Boxpadding="m"backgroundColor="mainBackground"flex={1}><BoxbackgroundColor="primaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="primaryCardText">
Primary Card
</Text></Box><BoxbackgroundColor="secondaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="secondaryCardText">
Secondary Card
</Text></Box><BoxmarginTop="m"><Switchvalue={darkMode}onValueChange={(value: boolean)=>setDarkMode(value)}/></Box></Box></ThemeProvider>);};exportdefaultApp;

Inspiration

Restyle is heavily inspired by https://styled-system.com.

Contributing

For help on setting up the repo locally, building, testing, and contributing please see CONTRIBUTING.md.

Code of Conduct

All developers who wish to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE.md for details.

About

A type-enforced system for building UI components in React Native with TypeScript.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

@shopify/restyle

RestyleTheme 2020-02-25 17_43_51

The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.

This library assumes that the UI is built upon a design system that (at the very least) defines a set of colors and spacing constants that lays as a foundation. While the library acknowledges that there can be exceptions to the system by allowing any style to be overridden, it keeps the developer most productive when one-off values are kept to a minimum.

Here's an example of how a view built with Restyle components could look:

import{ThemeProvider,createBox,createText,createRestyleComponent,createVariant,VariantProps,}from'@shopify/restyle';// See the "Defining Your Theme" readme section belowimporttheme,{Theme}from'./theme';constBox=createBox<Theme>();constText=createText<Theme>();constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>&React.ComponentProps<typeofBox>>([createVariant({themeKey: 'cardVariants'})],Box);constWelcome=()=>{return(<Boxflex={1}backgroundColor="mainBackground"paddingVertical="xl"paddingHorizontal="m"><Textvariant="header">Welcome</Text><BoxflexDirection={{phone: 'column',tablet: 'row',}}><Cardmargin="s"variant="secondary"><Textvariant="body">This is a simple example</Text></Card><Cardmargin="s"variant="primary"><Textvariant="body">Displaying how to use Restyle</Text></Card></Box></Box>);};constApp=()=>{return(<ThemeProvidertheme={theme}><Welcome/></ThemeProvider>);};

Restyle Component Workflow

Installation

Yarn

$ yarn add @shopify/restyle

NPM

$ npm install @shopify/restyle

Usage

Defining Your Theme

Any project using this library should have a global theme object. It specifies set values for spacing, colors, breakpoints, and more. These values are made available to Restyle components, so that you can for example write backgroundColor="cardPrimary" to use the named color from your theme. In fact, TypeScript enforces the backgroundColor property to only accept colors that have been defined in your theme, and autocompletes values for you in a modern editor.

Below is an example of how a basic theme could look. Make sure to read the sections below for more details on how to set up your different theme values.

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,cardPrimaryBackground: palette.purplePrimary,},spacing: {s: 8,m: 16,l: 24,xl: 40,},breakpoints: {phone: 0,tablet: 768,},};exporttypeTheme=typeoftheme;exportdefaulttheme;

This theme should be passed to a ThemeProvider at the top of your React tree:

import{ThemeProvider}from'@shopify/restyle';importthemefrom'./theme';constApp=()=>(<ThemeProvidertheme={theme}>{/* Rest of the app */}</ThemeProvider>);

Colors

When working with colors in a design system a common pattern is to have a palette including a number of base colors with darker and lighter shades, see for example the Polaris Color Palette.

This palette should preferrably not be directly included as values in the theme. The naming of colors in the theme object should instead be used to assign semantic meaning to the palette, see this example:

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,mainForeground: palette.black,cardPrimaryBackground: palette.purplePrimary,buttonPrimaryBackground: palette.purplePrimary,},};

Taking the time to define these semantic meanings comes with a number of benefits:

  • It's easy to understand where and in what context colors are applied throughout the app
  • If changes are made to the palette (e.g. the purple colors are changed to a shade of blue instead), we only have to update what the semantic names point to instead of updating all references to purplePrimary throughout the app.
  • Even though cardPrimaryBackground and buttonPrimaryBackground point to the same color in the example above, deciding that buttons should instead be green (while cards remain purple) becomes a trivial change.
  • A theme can easily be swapped at runtime.

Spacing

Spacing tends to follow multiples of a given base spacing number, for example 8. We prefer using the t-shirt size naming convention, because of the scalability of it (any number of x's can be prepended for smaller and larger sizes):

consttheme={spacing: {s: 8,m: 16,l: 24,xl: 40,},};

Breakpoints

Breakpoints are defined as minimum widths (inclusive) for different target screen sizes where we want to apply differing styles. Consider giving your breakpoints names that give a general idea of the type of device the user is using:

consttheme={breakpoints: {phone: 0,tablet: 768,largeTablet: 1024,},};

See the Responsive Values section to see how these can be used.

Accessing the Theme

If you need to manually access the theme outside of a component created with Restyle, use the useTheme hook:

constComponent=()=>{consttheme=useTheme<Theme>();const{cardPrimaryBackground}=theme.colors;// ...};

By doing this instead of directly importing the theme object, it becomes easy to swap the theme out during runtime to for example implement a dark mode switch in your app.

Predefined Components

This library comes with predefined functions to create a Box and Text component, as seen in action in the introductory example. These come as functions instead of ready-made components to give you a chance to provide the type of your theme object. Doing this will make all props that map to theme values have proper types configured, based on what's available in your theme.

Box

// In Box.tsximport{createBox}from'@shopify/restyle';import{Theme}from'./theme';constBox=createBox<Theme>();exportdefaultBox;

The Box component comes with the following Restyle functions: backgroundColor, opacity, visible, layout, spacing, border, shadow, position.

Text

// In Text.tsximport{createText}from'@shopify/restyle';import{Theme}from'./theme';constText=createText<Theme>();exportdefaultText;

The Text component comes with the following Restyle functions: color, opacity, visible, typography, textShadow, spacing. It also includes a variant that picks up styles under the textVariants key in your theme:

// In your themeconsttheme={
...,textVariants: {header: {fontFamily: 'ShopifySans-Bold',fontWeight: 'bold',fontSize: 34,lineHeight: 42.5,color: 'black',},subheader: {fontFamily: 'ShopifySans-SemiBold',fontWeight: '600',fontSize: 28,lineHeight: 36,color: 'black',},body: {fontFamily: 'ShopifySans',fontSize: 16,lineHeight: 24,color: 'black',},},}// In a component<Textvariant="header">Header</Text>

Custom Components

If you want to create your own component similar to Box or Text, but decide yourself which Restyle functions to use, use the createRestyleComponent helper:

import{createRestyleComponent,createVariant,spacing,SpacingProps,VariantProps}from'@shopify/restyle';import{Theme}from'./theme'typeProps=SpacingProps<Theme>&VariantProps<Theme,'cardVariants'>constCard=createRestyleComponent<Props>([spacing,createVariant({themeKey: 'cardVariants'})])exportdefaultCard

For more advanced components, you may want to instead use the useRestyle hook:

import{TouchableOpacity,View}from'react-native';import{useRestyle,spacing,border,backgroundColor,SpacingProps,BorderProps,BackgroundColorProps,}from'@shopify/restyle';importTextfrom'./Text';import{Theme}from'./theme';constrestyleFunctions=[spacing,border,backgroundColor];typeProps=SpacingProps<Theme>&BorderProps<Theme>&BackgroundColorProps<Theme>&{onPress: ()=>void;};constButton=({onPress, label, ...rest}: Props)=>{constprops=useRestyle(restyleFunctions,rest);return(<TouchableOpacityonPress={onPress}><View{...props}><Textvariant="buttonLabel">{label}</Text></View></TouchableOpacity>);};

Restyle Functions

Restyle functions are the bread and butter of Restyle. They specify how props should be mapped to values in a resulting style object, that can then be passed down to a React Native component. The props support responsive values and can be mapped to values in your theme.

Predefined Restyle Functions

The Restyle library comes with a number of predefined Restyle functions for your convenience.

Restyle FunctionPropsTheme Key
backgroundColorbackgroundColorcolors
colorcolorcolors
opacityopacitynone
visibledisplay (maps true / false to flex / none)none
spacingmargin, marginTop, marginRight, marginBottom, marginLeft, marginHorizontal, marginVertical, padding, paddingTop, paddingRight, paddingBottom, paddingLeft, paddingHorizontal, paddingVerticalspacing
layoutwidth, height, minWidth, maxWidth, minHeight, maxHeight, overflow, aspectRatio, alignContent, alignItems, alignSelf, justifyContent, flex, flexBasis, flexDirection, flexGrow, flexShrink, flexWrapnone
positionposition, top, right, bottom, leftnone
positionzIndexzIndices
borderborderBottomWidth, borderLeftWidth, borderRightWidth, borderStyle, borderTopWidth, borderWidthnone
borderborderColor, borderTopColor, borderRightColor, borderLeftColor, borderBottomColorcolors
borderborderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadiusborderRadii
shadowshadowOpacity, shadowOffset, shadowRadius, elevationnone
shadowshadowColorcolors
textShadowtextShadowOffset, textShadowRadiusnone
textShadowtextShadowColorcolors
typographyfontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, textAlign, textDecorationLine, textDecorationStyle, textTransformnone

Custom Restyle Functions

To define your own Restyle function, use the createRestyleFunction helper:

import{createRestyleFunction,createRestyleFunction}from'@shopify/restyle'consttransparency=createRestyleFunction({property: 'transparency',styleProperty: 'opacity',transform: ({value}: {value: number})=>1-value,});constTransparentComponent=createRestyleFunction([transparency])<TransparentComponenttransparency={0.5}/>

Arguments:

  • property: The name of the component prop that the function will receive tha value of.
  • styleProperty: The name of the property in the style object to map to. Defaults to the value of property.
  • transform({value, theme, themeKey}): An optional function that transforms the value of the prop to the value that will be inserted into the style object.
  • themeKey: An optional key in the theme to map values from, e.g. colors.

Variants

A variant is a form of Restyle function that maps a prop into multiple other props to use with Restyle functions. A variant needs to always map to a key in the theme.

// In themeconsttheme={// ...spacing: {s: 8,m: 16,l: 24,},colors: {cardRegularBackground: '#EEEEEE',},breakpoints: {phone: 0,tablet: 768,},cardVariants: {regular: {// We can refer to other values in the theme here, and use responsive propspadding: {phone: 's',tablet: 'm',},}elevated: {padding: {phone: 's',tablet: 'm',},shadowColor: '#000',shadowOpacity: 0.2,shadowOffset: {width: 0,height: 5},shadowRadius: 15,elevation: 5,}}}import{createVariant,createRestyleComponent,VariantProps}from'@shopify/restyle'constvariant=createVariant<Theme>({themeKey: 'cardVariants',defaults: {margin: {phone: 's',tablet: 'm',},backgroundColor: 'cardRegularBackground',}})constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>>([variant])<Cardvariant="elevated"/>

Arguments:

  • property: The name of the component prop that will map to a variant. Defaults to variant.
  • themeKey: A key in the theme to map values from. Unlike createRestyleFunction, this option is required to create a variant.
  • defaults: The default values to apply before applying anything from the values in the theme.

Responsive Values

Any prop powered by Restyle can optionally accept a value for each screen size, as defined by the breakpoints object in the theme:

// In your themeconsttheme={// ...breakpoints: {phone: 0,tablet: 768,}}// Props always accept either plain values<BoxflexDirection="row"/>// Or breakpoint-specific values<BoxflexDirection={{phone: 'column',tablet: 'row'}}/>

Overriding Styles

Any Restyle component also accepts a regular style property and will apply it after all other styles, which means that you can use this to do any overrides that you might find necessary.

<Boxmargin="s"padding="m"style={{backgroundColor: '#F00BAA',}}/>

Implementing Dark Mode

Of course, no app is complete without a dark mode. Here a simple example of how you would implement it:

importReact,{useState}from'react';import{Switch}from'react-native';import{ThemeProvider,createBox,createText}from'@shopify/restyle';exportconstpalette={purple: '#5A31F4',white: '#FFF',black: '#111',darkGray: '#333',lightGray: '#EEE',};consttheme={spacing: {s: 8,m: 16,},colors: {mainBackground: palette.lightGray,mainForeground: palette.black,primaryCardBackground: palette.purple,secondaryCardBackground: palette.white,primaryCardText: palette.white,secondaryCardText: palette.black,},breakpoints: {},textVariants: {body: {fontSize: 16,lineHeight: 24,color: 'mainForeground',},},cardVariants: {primary: {backgroundColor: 'primaryCardBackground',shadowOpacity: 0.3,},secondary: {backgroundColor: 'secondaryCardBackground',shadowOpacity: 0.1,},},};typeTheme=typeoftheme;constdarkTheme={
...theme,colors: {
...theme.colors,mainBackground: palette.black,mainForeground: palette.white,secondaryCardBackground: palette.darkGray,secondaryCardText: palette.white,},};constBox=createBox<Theme>();constText=createText<Theme>();constApp=()=>{const[darkMode,setDarkMode]=useState(false);return(<ThemeProvidertheme={darkMode ? darkTheme : theme}><Boxpadding="m"backgroundColor="mainBackground"flex={1}><BoxbackgroundColor="primaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="primaryCardText">
Primary Card
</Text></Box><BoxbackgroundColor="secondaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="secondaryCardText">
Secondary Card
</Text></Box><BoxmarginTop="m"><Switchvalue={darkMode}onValueChange={(value: boolean)=>setDarkMode(value)}/></Box></Box></ThemeProvider>);};exportdefaultApp;

Inspiration

Restyle is heavily inspired by https://styled-system.com.

Contributing

For help on setting up the repo locally, building, testing, and contributing please see CONTRIBUTING.md.

Code of Conduct

All developers who wish to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE.md for details.

About

A type-enforced system for building UI components in React Native with TypeScript.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@shopify/restyle

RestyleTheme 2020-02-25 17_43_51

The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.

This library assumes that the UI is built upon a design system that (at the very least) defines a set of colors and spacing constants that lays as a foundation. While the library acknowledges that there can be exceptions to the system by allowing any style to be overridden, it keeps the developer most productive when one-off values are kept to a minimum.

Here's an example of how a view built with Restyle components could look:

import{ThemeProvider,createBox,createText,createRestyleComponent,createVariant,VariantProps,}from'@shopify/restyle';// See the "Defining Your Theme" readme section belowimporttheme,{Theme}from'./theme';constBox=createBox<Theme>();constText=createText<Theme>();constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>&React.ComponentProps<typeofBox>>([createVariant({themeKey: 'cardVariants'})],Box);constWelcome=()=>{return(<Boxflex={1}backgroundColor="mainBackground"paddingVertical="xl"paddingHorizontal="m"><Textvariant="header">Welcome</Text><BoxflexDirection={{phone: 'column',tablet: 'row',}}><Cardmargin="s"variant="secondary"><Textvariant="body">This is a simple example</Text></Card><Cardmargin="s"variant="primary"><Textvariant="body">Displaying how to use Restyle</Text></Card></Box></Box>);};constApp=()=>{return(<ThemeProvidertheme={theme}><Welcome/></ThemeProvider>);};

Restyle Component Workflow

Installation

Yarn

$ yarn add @shopify/restyle

NPM

$ npm install @shopify/restyle

Usage

Defining Your Theme

Any project using this library should have a global theme object. It specifies set values for spacing, colors, breakpoints, and more. These values are made available to Restyle components, so that you can for example write backgroundColor="cardPrimary" to use the named color from your theme. In fact, TypeScript enforces the backgroundColor property to only accept colors that have been defined in your theme, and autocompletes values for you in a modern editor.

Below is an example of how a basic theme could look. Make sure to read the sections below for more details on how to set up your different theme values.

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,cardPrimaryBackground: palette.purplePrimary,},spacing: {s: 8,m: 16,l: 24,xl: 40,},breakpoints: {phone: 0,tablet: 768,},};exporttypeTheme=typeoftheme;exportdefaulttheme;

This theme should be passed to a ThemeProvider at the top of your React tree:

import{ThemeProvider}from'@shopify/restyle';importthemefrom'./theme';constApp=()=>(<ThemeProvidertheme={theme}>{/* Rest of the app */}</ThemeProvider>);

Colors

When working with colors in a design system a common pattern is to have a palette including a number of base colors with darker and lighter shades, see for example the Polaris Color Palette.

This palette should preferrably not be directly included as values in the theme. The naming of colors in the theme object should instead be used to assign semantic meaning to the palette, see this example:

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,mainForeground: palette.black,cardPrimaryBackground: palette.purplePrimary,buttonPrimaryBackground: palette.purplePrimary,},};

Taking the time to define these semantic meanings comes with a number of benefits:

  • It's easy to understand where and in what context colors are applied throughout the app
  • If changes are made to the palette (e.g. the purple colors are changed to a shade of blue instead), we only have to update what the semantic names point to instead of updating all references to purplePrimary throughout the app.
  • Even though cardPrimaryBackground and buttonPrimaryBackground point to the same color in the example above, deciding that buttons should instead be green (while cards remain purple) becomes a trivial change.
  • A theme can easily be swapped at runtime.

Spacing

Spacing tends to follow multiples of a given base spacing number, for example 8. We prefer using the t-shirt size naming convention, because of the scalability of it (any number of x's can be prepended for smaller and larger sizes):

consttheme={spacing: {s: 8,m: 16,l: 24,xl: 40,},};

Breakpoints

Breakpoints are defined as minimum widths (inclusive) for different target screen sizes where we want to apply differing styles. Consider giving your breakpoints names that give a general idea of the type of device the user is using:

consttheme={breakpoints: {phone: 0,tablet: 768,largeTablet: 1024,},};

See the Responsive Values section to see how these can be used.

Accessing the Theme

If you need to manually access the theme outside of a component created with Restyle, use the useTheme hook:

constComponent=()=>{consttheme=useTheme<Theme>();const{cardPrimaryBackground}=theme.colors;// ...};

By doing this instead of directly importing the theme object, it becomes easy to swap the theme out during runtime to for example implement a dark mode switch in your app.

Predefined Components

This library comes with predefined functions to create a Box and Text component, as seen in action in the introductory example. These come as functions instead of ready-made components to give you a chance to provide the type of your theme object. Doing this will make all props that map to theme values have proper types configured, based on what's available in your theme.

Box

// In Box.tsximport{createBox}from'@shopify/restyle';import{Theme}from'./theme';constBox=createBox<Theme>();exportdefaultBox;

The Box component comes with the following Restyle functions: backgroundColor, opacity, visible, layout, spacing, border, shadow, position.

Text

// In Text.tsximport{createText}from'@shopify/restyle';import{Theme}from'./theme';constText=createText<Theme>();exportdefaultText;

The Text component comes with the following Restyle functions: color, opacity, visible, typography, textShadow, spacing. It also includes a variant that picks up styles under the textVariants key in your theme:

// In your themeconsttheme={
...,textVariants: {header: {fontFamily: 'ShopifySans-Bold',fontWeight: 'bold',fontSize: 34,lineHeight: 42.5,color: 'black',},subheader: {fontFamily: 'ShopifySans-SemiBold',fontWeight: '600',fontSize: 28,lineHeight: 36,color: 'black',},body: {fontFamily: 'ShopifySans',fontSize: 16,lineHeight: 24,color: 'black',},},}// In a component<Textvariant="header">Header</Text>

Custom Components

If you want to create your own component similar to Box or Text, but decide yourself which Restyle functions to use, use the createRestyleComponent helper:

import{createRestyleComponent,createVariant,spacing,SpacingProps,VariantProps}from'@shopify/restyle';import{Theme}from'./theme'typeProps=SpacingProps<Theme>&VariantProps<Theme,'cardVariants'>constCard=createRestyleComponent<Props>([spacing,createVariant({themeKey: 'cardVariants'})])exportdefaultCard

For more advanced components, you may want to instead use the useRestyle hook:

import{TouchableOpacity,View}from'react-native';import{useRestyle,spacing,border,backgroundColor,SpacingProps,BorderProps,BackgroundColorProps,}from'@shopify/restyle';importTextfrom'./Text';import{Theme}from'./theme';constrestyleFunctions=[spacing,border,backgroundColor];typeProps=SpacingProps<Theme>&BorderProps<Theme>&BackgroundColorProps<Theme>&{onPress: ()=>void;};constButton=({onPress, label, ...rest}: Props)=>{constprops=useRestyle(restyleFunctions,rest);return(<TouchableOpacityonPress={onPress}><View{...props}><Textvariant="buttonLabel">{label}</Text></View></TouchableOpacity>);};

Restyle Functions

Restyle functions are the bread and butter of Restyle. They specify how props should be mapped to values in a resulting style object, that can then be passed down to a React Native component. The props support responsive values and can be mapped to values in your theme.

Predefined Restyle Functions

The Restyle library comes with a number of predefined Restyle functions for your convenience.

Restyle FunctionPropsTheme Key
backgroundColorbackgroundColorcolors
colorcolorcolors
opacityopacitynone
visibledisplay (maps true / false to flex / none)none
spacingmargin, marginTop, marginRight, marginBottom, marginLeft, marginHorizontal, marginVertical, padding, paddingTop, paddingRight, paddingBottom, paddingLeft, paddingHorizontal, paddingVerticalspacing
layoutwidth, height, minWidth, maxWidth, minHeight, maxHeight, overflow, aspectRatio, alignContent, alignItems, alignSelf, justifyContent, flex, flexBasis, flexDirection, flexGrow, flexShrink, flexWrapnone
positionposition, top, right, bottom, leftnone
positionzIndexzIndices
borderborderBottomWidth, borderLeftWidth, borderRightWidth, borderStyle, borderTopWidth, borderWidthnone
borderborderColor, borderTopColor, borderRightColor, borderLeftColor, borderBottomColorcolors
borderborderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadiusborderRadii
shadowshadowOpacity, shadowOffset, shadowRadius, elevationnone
shadowshadowColorcolors
textShadowtextShadowOffset, textShadowRadiusnone
textShadowtextShadowColorcolors
typographyfontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, textAlign, textDecorationLine, textDecorationStyle, textTransformnone

Custom Restyle Functions

To define your own Restyle function, use the createRestyleFunction helper:

import{createRestyleFunction,createRestyleFunction}from'@shopify/restyle'consttransparency=createRestyleFunction({property: 'transparency',styleProperty: 'opacity',transform: ({value}: {value: number})=>1-value,});constTransparentComponent=createRestyleFunction([transparency])<TransparentComponenttransparency={0.5}/>

Arguments:

  • property: The name of the component prop that the function will receive tha value of.
  • styleProperty: The name of the property in the style object to map to. Defaults to the value of property.
  • transform({value, theme, themeKey}): An optional function that transforms the value of the prop to the value that will be inserted into the style object.
  • themeKey: An optional key in the theme to map values from, e.g. colors.

Variants

A variant is a form of Restyle function that maps a prop into multiple other props to use with Restyle functions. A variant needs to always map to a key in the theme.

// In themeconsttheme={// ...spacing: {s: 8,m: 16,l: 24,},colors: {cardRegularBackground: '#EEEEEE',},breakpoints: {phone: 0,tablet: 768,},cardVariants: {regular: {// We can refer to other values in the theme here, and use responsive propspadding: {phone: 's',tablet: 'm',},}elevated: {padding: {phone: 's',tablet: 'm',},shadowColor: '#000',shadowOpacity: 0.2,shadowOffset: {width: 0,height: 5},shadowRadius: 15,elevation: 5,}}}import{createVariant,createRestyleComponent,VariantProps}from'@shopify/restyle'constvariant=createVariant<Theme>({themeKey: 'cardVariants',defaults: {margin: {phone: 's',tablet: 'm',},backgroundColor: 'cardRegularBackground',}})constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>>([variant])<Cardvariant="elevated"/>

Arguments:

  • property: The name of the component prop that will map to a variant. Defaults to variant.
  • themeKey: A key in the theme to map values from. Unlike createRestyleFunction, this option is required to create a variant.
  • defaults: The default values to apply before applying anything from the values in the theme.

Responsive Values

Any prop powered by Restyle can optionally accept a value for each screen size, as defined by the breakpoints object in the theme:

// In your themeconsttheme={// ...breakpoints: {phone: 0,tablet: 768,}}// Props always accept either plain values<BoxflexDirection="row"/>// Or breakpoint-specific values<BoxflexDirection={{phone: 'column',tablet: 'row'}}/>

Overriding Styles

Any Restyle component also accepts a regular style property and will apply it after all other styles, which means that you can use this to do any overrides that you might find necessary.

<Boxmargin="s"padding="m"style={{backgroundColor: '#F00BAA',}}/>

Implementing Dark Mode

Of course, no app is complete without a dark mode. Here a simple example of how you would implement it:

importReact,{useState}from'react';import{Switch}from'react-native';import{ThemeProvider,createBox,createText}from'@shopify/restyle';exportconstpalette={purple: '#5A31F4',white: '#FFF',black: '#111',darkGray: '#333',lightGray: '#EEE',};consttheme={spacing: {s: 8,m: 16,},colors: {mainBackground: palette.lightGray,mainForeground: palette.black,primaryCardBackground: palette.purple,secondaryCardBackground: palette.white,primaryCardText: palette.white,secondaryCardText: palette.black,},breakpoints: {},textVariants: {body: {fontSize: 16,lineHeight: 24,color: 'mainForeground',},},cardVariants: {primary: {backgroundColor: 'primaryCardBackground',shadowOpacity: 0.3,},secondary: {backgroundColor: 'secondaryCardBackground',shadowOpacity: 0.1,},},};typeTheme=typeoftheme;constdarkTheme={
...theme,colors: {
...theme.colors,mainBackground: palette.black,mainForeground: palette.white,secondaryCardBackground: palette.darkGray,secondaryCardText: palette.white,},};constBox=createBox<Theme>();constText=createText<Theme>();constApp=()=>{const[darkMode,setDarkMode]=useState(false);return(<ThemeProvidertheme={darkMode ? darkTheme : theme}><Boxpadding="m"backgroundColor="mainBackground"flex={1}><BoxbackgroundColor="primaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="primaryCardText">
Primary Card
</Text></Box><BoxbackgroundColor="secondaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="secondaryCardText">
Secondary Card
</Text></Box><BoxmarginTop="m"><Switchvalue={darkMode}onValueChange={(value: boolean)=>setDarkMode(value)}/></Box></Box></ThemeProvider>);};exportdefaultApp;

Inspiration

Restyle is heavily inspired by https://styled-system.com.

Contributing

For help on setting up the repo locally, building, testing, and contributing please see CONTRIBUTING.md.

Code of Conduct

All developers who wish to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE.md for details.

About

A type-enforced system for building UI components in React Native with TypeScript.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@shopify/restyle

RestyleTheme 2020-02-25 17_43_51

The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.

This library assumes that the UI is built upon a design system that (at the very least) defines a set of colors and spacing constants that lays as a foundation. While the library acknowledges that there can be exceptions to the system by allowing any style to be overridden, it keeps the developer most productive when one-off values are kept to a minimum.

Here's an example of how a view built with Restyle components could look:

import{ThemeProvider,createBox,createText,createRestyleComponent,createVariant,VariantProps,}from'@shopify/restyle';// See the "Defining Your Theme" readme section belowimporttheme,{Theme}from'./theme';constBox=createBox<Theme>();constText=createText<Theme>();constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>&React.ComponentProps<typeofBox>>([createVariant({themeKey: 'cardVariants'})],Box);constWelcome=()=>{return(<Boxflex={1}backgroundColor="mainBackground"paddingVertical="xl"paddingHorizontal="m"><Textvariant="header">Welcome</Text><BoxflexDirection={{phone: 'column',tablet: 'row',}}><Cardmargin="s"variant="secondary"><Textvariant="body">This is a simple example</Text></Card><Cardmargin="s"variant="primary"><Textvariant="body">Displaying how to use Restyle</Text></Card></Box></Box>);};constApp=()=>{return(<ThemeProvidertheme={theme}><Welcome/></ThemeProvider>);};

Restyle Component Workflow

Installation

Yarn

$ yarn add @shopify/restyle

NPM

$ npm install @shopify/restyle

Usage

Defining Your Theme

Any project using this library should have a global theme object. It specifies set values for spacing, colors, breakpoints, and more. These values are made available to Restyle components, so that you can for example write backgroundColor="cardPrimary" to use the named color from your theme. In fact, TypeScript enforces the backgroundColor property to only accept colors that have been defined in your theme, and autocompletes values for you in a modern editor.

Below is an example of how a basic theme could look. Make sure to read the sections below for more details on how to set up your different theme values.

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,cardPrimaryBackground: palette.purplePrimary,},spacing: {s: 8,m: 16,l: 24,xl: 40,},breakpoints: {phone: 0,tablet: 768,},};exporttypeTheme=typeoftheme;exportdefaulttheme;

This theme should be passed to a ThemeProvider at the top of your React tree:

import{ThemeProvider}from'@shopify/restyle';importthemefrom'./theme';constApp=()=>(<ThemeProvidertheme={theme}>{/* Rest of the app */}</ThemeProvider>);

Colors

When working with colors in a design system a common pattern is to have a palette including a number of base colors with darker and lighter shades, see for example the Polaris Color Palette.

This palette should preferrably not be directly included as values in the theme. The naming of colors in the theme object should instead be used to assign semantic meaning to the palette, see this example:

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,mainForeground: palette.black,cardPrimaryBackground: palette.purplePrimary,buttonPrimaryBackground: palette.purplePrimary,},};

Taking the time to define these semantic meanings comes with a number of benefits:

  • It's easy to understand where and in what context colors are applied throughout the app
  • If changes are made to the palette (e.g. the purple colors are changed to a shade of blue instead), we only have to update what the semantic names point to instead of updating all references to purplePrimary throughout the app.
  • Even though cardPrimaryBackground and buttonPrimaryBackground point to the same color in the example above, deciding that buttons should instead be green (while cards remain purple) becomes a trivial change.
  • A theme can easily be swapped at runtime.

Spacing

Spacing tends to follow multiples of a given base spacing number, for example 8. We prefer using the t-shirt size naming convention, because of the scalability of it (any number of x's can be prepended for smaller and larger sizes):

consttheme={spacing: {s: 8,m: 16,l: 24,xl: 40,},};

Breakpoints

Breakpoints are defined as minimum widths (inclusive) for different target screen sizes where we want to apply differing styles. Consider giving your breakpoints names that give a general idea of the type of device the user is using:

consttheme={breakpoints: {phone: 0,tablet: 768,largeTablet: 1024,},};

See the Responsive Values section to see how these can be used.

Accessing the Theme

If you need to manually access the theme outside of a component created with Restyle, use the useTheme hook:

constComponent=()=>{consttheme=useTheme<Theme>();const{cardPrimaryBackground}=theme.colors;// ...};

By doing this instead of directly importing the theme object, it becomes easy to swap the theme out during runtime to for example implement a dark mode switch in your app.

Predefined Components

This library comes with predefined functions to create a Box and Text component, as seen in action in the introductory example. These come as functions instead of ready-made components to give you a chance to provide the type of your theme object. Doing this will make all props that map to theme values have proper types configured, based on what's available in your theme.

Box

// In Box.tsximport{createBox}from'@shopify/restyle';import{Theme}from'./theme';constBox=createBox<Theme>();exportdefaultBox;

The Box component comes with the following Restyle functions: backgroundColor, opacity, visible, layout, spacing, border, shadow, position.

Text

// In Text.tsximport{createText}from'@shopify/restyle';import{Theme}from'./theme';constText=createText<Theme>();exportdefaultText;

The Text component comes with the following Restyle functions: color, opacity, visible, typography, textShadow, spacing. It also includes a variant that picks up styles under the textVariants key in your theme:

// In your themeconsttheme={
...,textVariants: {header: {fontFamily: 'ShopifySans-Bold',fontWeight: 'bold',fontSize: 34,lineHeight: 42.5,color: 'black',},subheader: {fontFamily: 'ShopifySans-SemiBold',fontWeight: '600',fontSize: 28,lineHeight: 36,color: 'black',},body: {fontFamily: 'ShopifySans',fontSize: 16,lineHeight: 24,color: 'black',},},}// In a component<Textvariant="header">Header</Text>

Custom Components

If you want to create your own component similar to Box or Text, but decide yourself which Restyle functions to use, use the createRestyleComponent helper:

import{createRestyleComponent,createVariant,spacing,SpacingProps,VariantProps}from'@shopify/restyle';import{Theme}from'./theme'typeProps=SpacingProps<Theme>&VariantProps<Theme,'cardVariants'>constCard=createRestyleComponent<Props>([spacing,createVariant({themeKey: 'cardVariants'})])exportdefaultCard

For more advanced components, you may want to instead use the useRestyle hook:

import{TouchableOpacity,View}from'react-native';import{useRestyle,spacing,border,backgroundColor,SpacingProps,BorderProps,BackgroundColorProps,}from'@shopify/restyle';importTextfrom'./Text';import{Theme}from'./theme';constrestyleFunctions=[spacing,border,backgroundColor];typeProps=SpacingProps<Theme>&BorderProps<Theme>&BackgroundColorProps<Theme>&{onPress: ()=>void;};constButton=({onPress, label, ...rest}: Props)=>{constprops=useRestyle(restyleFunctions,rest);return(<TouchableOpacityonPress={onPress}><View{...props}><Textvariant="buttonLabel">{label}</Text></View></TouchableOpacity>);};

Restyle Functions

Restyle functions are the bread and butter of Restyle. They specify how props should be mapped to values in a resulting style object, that can then be passed down to a React Native component. The props support responsive values and can be mapped to values in your theme.

Predefined Restyle Functions

The Restyle library comes with a number of predefined Restyle functions for your convenience.

Restyle FunctionPropsTheme Key
backgroundColorbackgroundColorcolors
colorcolorcolors
opacityopacitynone
visibledisplay (maps true / false to flex / none)none
spacingmargin, marginTop, marginRight, marginBottom, marginLeft, marginHorizontal, marginVertical, padding, paddingTop, paddingRight, paddingBottom, paddingLeft, paddingHorizontal, paddingVerticalspacing
layoutwidth, height, minWidth, maxWidth, minHeight, maxHeight, overflow, aspectRatio, alignContent, alignItems, alignSelf, justifyContent, flex, flexBasis, flexDirection, flexGrow, flexShrink, flexWrapnone
positionposition, top, right, bottom, leftnone
positionzIndexzIndices
borderborderBottomWidth, borderLeftWidth, borderRightWidth, borderStyle, borderTopWidth, borderWidthnone
borderborderColor, borderTopColor, borderRightColor, borderLeftColor, borderBottomColorcolors
borderborderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadiusborderRadii
shadowshadowOpacity, shadowOffset, shadowRadius, elevationnone
shadowshadowColorcolors
textShadowtextShadowOffset, textShadowRadiusnone
textShadowtextShadowColorcolors
typographyfontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, textAlign, textDecorationLine, textDecorationStyle, textTransformnone

Custom Restyle Functions

To define your own Restyle function, use the createRestyleFunction helper:

import{createRestyleFunction,createRestyleFunction}from'@shopify/restyle'consttransparency=createRestyleFunction({property: 'transparency',styleProperty: 'opacity',transform: ({value}: {value: number})=>1-value,});constTransparentComponent=createRestyleFunction([transparency])<TransparentComponenttransparency={0.5}/>

Arguments:

  • property: The name of the component prop that the function will receive tha value of.
  • styleProperty: The name of the property in the style object to map to. Defaults to the value of property.
  • transform({value, theme, themeKey}): An optional function that transforms the value of the prop to the value that will be inserted into the style object.
  • themeKey: An optional key in the theme to map values from, e.g. colors.

Variants

A variant is a form of Restyle function that maps a prop into multiple other props to use with Restyle functions. A variant needs to always map to a key in the theme.

// In themeconsttheme={// ...spacing: {s: 8,m: 16,l: 24,},colors: {cardRegularBackground: '#EEEEEE',},breakpoints: {phone: 0,tablet: 768,},cardVariants: {regular: {// We can refer to other values in the theme here, and use responsive propspadding: {phone: 's',tablet: 'm',},}elevated: {padding: {phone: 's',tablet: 'm',},shadowColor: '#000',shadowOpacity: 0.2,shadowOffset: {width: 0,height: 5},shadowRadius: 15,elevation: 5,}}}import{createVariant,createRestyleComponent,VariantProps}from'@shopify/restyle'constvariant=createVariant<Theme>({themeKey: 'cardVariants',defaults: {margin: {phone: 's',tablet: 'm',},backgroundColor: 'cardRegularBackground',}})constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>>([variant])<Cardvariant="elevated"/>

Arguments:

  • property: The name of the component prop that will map to a variant. Defaults to variant.
  • themeKey: A key in the theme to map values from. Unlike createRestyleFunction, this option is required to create a variant.
  • defaults: The default values to apply before applying anything from the values in the theme.

Responsive Values

Any prop powered by Restyle can optionally accept a value for each screen size, as defined by the breakpoints object in the theme:

// In your themeconsttheme={// ...breakpoints: {phone: 0,tablet: 768,}}// Props always accept either plain values<BoxflexDirection="row"/>// Or breakpoint-specific values<BoxflexDirection={{phone: 'column',tablet: 'row'}}/>

Overriding Styles

Any Restyle component also accepts a regular style property and will apply it after all other styles, which means that you can use this to do any overrides that you might find necessary.

<Boxmargin="s"padding="m"style={{backgroundColor: '#F00BAA',}}/>

Implementing Dark Mode

Of course, no app is complete without a dark mode. Here a simple example of how you would implement it:

importReact,{useState}from'react';import{Switch}from'react-native';import{ThemeProvider,createBox,createText}from'@shopify/restyle';exportconstpalette={purple: '#5A31F4',white: '#FFF',black: '#111',darkGray: '#333',lightGray: '#EEE',};consttheme={spacing: {s: 8,m: 16,},colors: {mainBackground: palette.lightGray,mainForeground: palette.black,primaryCardBackground: palette.purple,secondaryCardBackground: palette.white,primaryCardText: palette.white,secondaryCardText: palette.black,},breakpoints: {},textVariants: {body: {fontSize: 16,lineHeight: 24,color: 'mainForeground',},},cardVariants: {primary: {backgroundColor: 'primaryCardBackground',shadowOpacity: 0.3,},secondary: {backgroundColor: 'secondaryCardBackground',shadowOpacity: 0.1,},},};typeTheme=typeoftheme;constdarkTheme={
...theme,colors: {
...theme.colors,mainBackground: palette.black,mainForeground: palette.white,secondaryCardBackground: palette.darkGray,secondaryCardText: palette.white,},};constBox=createBox<Theme>();constText=createText<Theme>();constApp=()=>{const[darkMode,setDarkMode]=useState(false);return(<ThemeProvidertheme={darkMode ? darkTheme : theme}><Boxpadding="m"backgroundColor="mainBackground"flex={1}><BoxbackgroundColor="primaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="primaryCardText">
Primary Card
</Text></Box><BoxbackgroundColor="secondaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="secondaryCardText">
Secondary Card
</Text></Box><BoxmarginTop="m"><Switchvalue={darkMode}onValueChange={(value: boolean)=>setDarkMode(value)}/></Box></Box></ThemeProvider>);};exportdefaultApp;

Inspiration

Restyle is heavily inspired by https://styled-system.com.

Contributing

For help on setting up the repo locally, building, testing, and contributing please see CONTRIBUTING.md.

Code of Conduct

All developers who wish to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE.md for details.

About

A type-enforced system for building UI components in React Native with TypeScript.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

@shopify/restyle

RestyleTheme 2020-02-25 17_43_51

The Restyle library provides a type-enforced system for building UI components in React Native with TypeScript. It's a library for building UI libraries, with themability as the core focus.

This library assumes that the UI is built upon a design system that (at the very least) defines a set of colors and spacing constants that lays as a foundation. While the library acknowledges that there can be exceptions to the system by allowing any style to be overridden, it keeps the developer most productive when one-off values are kept to a minimum.

Here's an example of how a view built with Restyle components could look:

import{ThemeProvider,createBox,createText,createRestyleComponent,createVariant,VariantProps,}from'@shopify/restyle';// See the "Defining Your Theme" readme section belowimporttheme,{Theme}from'./theme';constBox=createBox<Theme>();constText=createText<Theme>();constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>&React.ComponentProps<typeofBox>>([createVariant({themeKey: 'cardVariants'})],Box);constWelcome=()=>{return(<Boxflex={1}backgroundColor="mainBackground"paddingVertical="xl"paddingHorizontal="m"><Textvariant="header">Welcome</Text><BoxflexDirection={{phone: 'column',tablet: 'row',}}><Cardmargin="s"variant="secondary"><Textvariant="body">This is a simple example</Text></Card><Cardmargin="s"variant="primary"><Textvariant="body">Displaying how to use Restyle</Text></Card></Box></Box>);};constApp=()=>{return(<ThemeProvidertheme={theme}><Welcome/></ThemeProvider>);};

Restyle Component Workflow

Installation

Yarn

$ yarn add @shopify/restyle

NPM

$ npm install @shopify/restyle

Usage

Defining Your Theme

Any project using this library should have a global theme object. It specifies set values for spacing, colors, breakpoints, and more. These values are made available to Restyle components, so that you can for example write backgroundColor="cardPrimary" to use the named color from your theme. In fact, TypeScript enforces the backgroundColor property to only accept colors that have been defined in your theme, and autocompletes values for you in a modern editor.

Below is an example of how a basic theme could look. Make sure to read the sections below for more details on how to set up your different theme values.

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,cardPrimaryBackground: palette.purplePrimary,},spacing: {s: 8,m: 16,l: 24,xl: 40,},breakpoints: {phone: 0,tablet: 768,},};exporttypeTheme=typeoftheme;exportdefaulttheme;

This theme should be passed to a ThemeProvider at the top of your React tree:

import{ThemeProvider}from'@shopify/restyle';importthemefrom'./theme';constApp=()=>(<ThemeProvidertheme={theme}>{/* Rest of the app */}</ThemeProvider>);

Colors

When working with colors in a design system a common pattern is to have a palette including a number of base colors with darker and lighter shades, see for example the Polaris Color Palette.

This palette should preferrably not be directly included as values in the theme. The naming of colors in the theme object should instead be used to assign semantic meaning to the palette, see this example:

constpalette={purpleLight: '#8C6FF7',purplePrimary: '#5A31F4',purpleDark: '#3F22AB',greenLight: '#56DCBA',greenPrimary: '#0ECD9D',greenDark: '#0A906E',black: '#0B0B0B',white: '#F0F2F3',};consttheme={colors: {mainBackground: palette.white,mainForeground: palette.black,cardPrimaryBackground: palette.purplePrimary,buttonPrimaryBackground: palette.purplePrimary,},};

Taking the time to define these semantic meanings comes with a number of benefits:

  • It's easy to understand where and in what context colors are applied throughout the app
  • If changes are made to the palette (e.g. the purple colors are changed to a shade of blue instead), we only have to update what the semantic names point to instead of updating all references to purplePrimary throughout the app.
  • Even though cardPrimaryBackground and buttonPrimaryBackground point to the same color in the example above, deciding that buttons should instead be green (while cards remain purple) becomes a trivial change.
  • A theme can easily be swapped at runtime.

Spacing

Spacing tends to follow multiples of a given base spacing number, for example 8. We prefer using the t-shirt size naming convention, because of the scalability of it (any number of x's can be prepended for smaller and larger sizes):

consttheme={spacing: {s: 8,m: 16,l: 24,xl: 40,},};

Breakpoints

Breakpoints are defined as minimum widths (inclusive) for different target screen sizes where we want to apply differing styles. Consider giving your breakpoints names that give a general idea of the type of device the user is using:

consttheme={breakpoints: {phone: 0,tablet: 768,largeTablet: 1024,},};

See the Responsive Values section to see how these can be used.

Accessing the Theme

If you need to manually access the theme outside of a component created with Restyle, use the useTheme hook:

constComponent=()=>{consttheme=useTheme<Theme>();const{cardPrimaryBackground}=theme.colors;// ...};

By doing this instead of directly importing the theme object, it becomes easy to swap the theme out during runtime to for example implement a dark mode switch in your app.

Predefined Components

This library comes with predefined functions to create a Box and Text component, as seen in action in the introductory example. These come as functions instead of ready-made components to give you a chance to provide the type of your theme object. Doing this will make all props that map to theme values have proper types configured, based on what's available in your theme.

Box

// In Box.tsximport{createBox}from'@shopify/restyle';import{Theme}from'./theme';constBox=createBox<Theme>();exportdefaultBox;

The Box component comes with the following Restyle functions: backgroundColor, opacity, visible, layout, spacing, border, shadow, position.

Text

// In Text.tsximport{createText}from'@shopify/restyle';import{Theme}from'./theme';constText=createText<Theme>();exportdefaultText;

The Text component comes with the following Restyle functions: color, opacity, visible, typography, textShadow, spacing. It also includes a variant that picks up styles under the textVariants key in your theme:

// In your themeconsttheme={
...,textVariants: {header: {fontFamily: 'ShopifySans-Bold',fontWeight: 'bold',fontSize: 34,lineHeight: 42.5,color: 'black',},subheader: {fontFamily: 'ShopifySans-SemiBold',fontWeight: '600',fontSize: 28,lineHeight: 36,color: 'black',},body: {fontFamily: 'ShopifySans',fontSize: 16,lineHeight: 24,color: 'black',},},}// In a component<Textvariant="header">Header</Text>

Custom Components

If you want to create your own component similar to Box or Text, but decide yourself which Restyle functions to use, use the createRestyleComponent helper:

import{createRestyleComponent,createVariant,spacing,SpacingProps,VariantProps}from'@shopify/restyle';import{Theme}from'./theme'typeProps=SpacingProps<Theme>&VariantProps<Theme,'cardVariants'>constCard=createRestyleComponent<Props>([spacing,createVariant({themeKey: 'cardVariants'})])exportdefaultCard

For more advanced components, you may want to instead use the useRestyle hook:

import{TouchableOpacity,View}from'react-native';import{useRestyle,spacing,border,backgroundColor,SpacingProps,BorderProps,BackgroundColorProps,}from'@shopify/restyle';importTextfrom'./Text';import{Theme}from'./theme';constrestyleFunctions=[spacing,border,backgroundColor];typeProps=SpacingProps<Theme>&BorderProps<Theme>&BackgroundColorProps<Theme>&{onPress: ()=>void;};constButton=({onPress, label, ...rest}: Props)=>{constprops=useRestyle(restyleFunctions,rest);return(<TouchableOpacityonPress={onPress}><View{...props}><Textvariant="buttonLabel">{label}</Text></View></TouchableOpacity>);};

Restyle Functions

Restyle functions are the bread and butter of Restyle. They specify how props should be mapped to values in a resulting style object, that can then be passed down to a React Native component. The props support responsive values and can be mapped to values in your theme.

Predefined Restyle Functions

The Restyle library comes with a number of predefined Restyle functions for your convenience.

Restyle FunctionPropsTheme Key
backgroundColorbackgroundColorcolors
colorcolorcolors
opacityopacitynone
visibledisplay (maps true / false to flex / none)none
spacingmargin, marginTop, marginRight, marginBottom, marginLeft, marginHorizontal, marginVertical, padding, paddingTop, paddingRight, paddingBottom, paddingLeft, paddingHorizontal, paddingVerticalspacing
layoutwidth, height, minWidth, maxWidth, minHeight, maxHeight, overflow, aspectRatio, alignContent, alignItems, alignSelf, justifyContent, flex, flexBasis, flexDirection, flexGrow, flexShrink, flexWrapnone
positionposition, top, right, bottom, leftnone
positionzIndexzIndices
borderborderBottomWidth, borderLeftWidth, borderRightWidth, borderStyle, borderTopWidth, borderWidthnone
borderborderColor, borderTopColor, borderRightColor, borderLeftColor, borderBottomColorcolors
borderborderRadius, borderBottomLeftRadius, borderBottomRightRadius, borderTopLeftRadius, borderTopRightRadiusborderRadii
shadowshadowOpacity, shadowOffset, shadowRadius, elevationnone
shadowshadowColorcolors
textShadowtextShadowOffset, textShadowRadiusnone
textShadowtextShadowColorcolors
typographyfontFamily, fontSize, fontStyle, fontWeight, letterSpacing, lineHeight, textAlign, textDecorationLine, textDecorationStyle, textTransformnone

Custom Restyle Functions

To define your own Restyle function, use the createRestyleFunction helper:

import{createRestyleFunction,createRestyleFunction}from'@shopify/restyle'consttransparency=createRestyleFunction({property: 'transparency',styleProperty: 'opacity',transform: ({value}: {value: number})=>1-value,});constTransparentComponent=createRestyleFunction([transparency])<TransparentComponenttransparency={0.5}/>

Arguments:

  • property: The name of the component prop that the function will receive tha value of.
  • styleProperty: The name of the property in the style object to map to. Defaults to the value of property.
  • transform({value, theme, themeKey}): An optional function that transforms the value of the prop to the value that will be inserted into the style object.
  • themeKey: An optional key in the theme to map values from, e.g. colors.

Variants

A variant is a form of Restyle function that maps a prop into multiple other props to use with Restyle functions. A variant needs to always map to a key in the theme.

// In themeconsttheme={// ...spacing: {s: 8,m: 16,l: 24,},colors: {cardRegularBackground: '#EEEEEE',},breakpoints: {phone: 0,tablet: 768,},cardVariants: {regular: {// We can refer to other values in the theme here, and use responsive propspadding: {phone: 's',tablet: 'm',},}elevated: {padding: {phone: 's',tablet: 'm',},shadowColor: '#000',shadowOpacity: 0.2,shadowOffset: {width: 0,height: 5},shadowRadius: 15,elevation: 5,}}}import{createVariant,createRestyleComponent,VariantProps}from'@shopify/restyle'constvariant=createVariant<Theme>({themeKey: 'cardVariants',defaults: {margin: {phone: 's',tablet: 'm',},backgroundColor: 'cardRegularBackground',}})constCard=createRestyleComponent<VariantProps<Theme,'cardVariants'>>([variant])<Cardvariant="elevated"/>

Arguments:

  • property: The name of the component prop that will map to a variant. Defaults to variant.
  • themeKey: A key in the theme to map values from. Unlike createRestyleFunction, this option is required to create a variant.
  • defaults: The default values to apply before applying anything from the values in the theme.

Responsive Values

Any prop powered by Restyle can optionally accept a value for each screen size, as defined by the breakpoints object in the theme:

// In your themeconsttheme={// ...breakpoints: {phone: 0,tablet: 768,}}// Props always accept either plain values<BoxflexDirection="row"/>// Or breakpoint-specific values<BoxflexDirection={{phone: 'column',tablet: 'row'}}/>

Overriding Styles

Any Restyle component also accepts a regular style property and will apply it after all other styles, which means that you can use this to do any overrides that you might find necessary.

<Boxmargin="s"padding="m"style={{backgroundColor: '#F00BAA',}}/>

Implementing Dark Mode

Of course, no app is complete without a dark mode. Here a simple example of how you would implement it:

importReact,{useState}from'react';import{Switch}from'react-native';import{ThemeProvider,createBox,createText}from'@shopify/restyle';exportconstpalette={purple: '#5A31F4',white: '#FFF',black: '#111',darkGray: '#333',lightGray: '#EEE',};consttheme={spacing: {s: 8,m: 16,},colors: {mainBackground: palette.lightGray,mainForeground: palette.black,primaryCardBackground: palette.purple,secondaryCardBackground: palette.white,primaryCardText: palette.white,secondaryCardText: palette.black,},breakpoints: {},textVariants: {body: {fontSize: 16,lineHeight: 24,color: 'mainForeground',},},cardVariants: {primary: {backgroundColor: 'primaryCardBackground',shadowOpacity: 0.3,},secondary: {backgroundColor: 'secondaryCardBackground',shadowOpacity: 0.1,},},};typeTheme=typeoftheme;constdarkTheme={
...theme,colors: {
...theme.colors,mainBackground: palette.black,mainForeground: palette.white,secondaryCardBackground: palette.darkGray,secondaryCardText: palette.white,},};constBox=createBox<Theme>();constText=createText<Theme>();constApp=()=>{const[darkMode,setDarkMode]=useState(false);return(<ThemeProvidertheme={darkMode ? darkTheme : theme}><Boxpadding="m"backgroundColor="mainBackground"flex={1}><BoxbackgroundColor="primaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="primaryCardText">
Primary Card
</Text></Box><BoxbackgroundColor="secondaryCardBackground"margin="s"padding="m"flexGrow={1}><Textvariant="body"color="secondaryCardText">
Secondary Card
</Text></Box><BoxmarginTop="m"><Switchvalue={darkMode}onValueChange={(value: boolean)=>setDarkMode(value)}/></Box></Box></ThemeProvider>);};exportdefaultApp;

Inspiration

Restyle is heavily inspired by https://styled-system.com.

Contributing

For help on setting up the repo locally, building, testing, and contributing please see CONTRIBUTING.md.

Code of Conduct

All developers who wish to contribute through code or issues, take a look at the CODE_OF_CONDUCT.md.

License

MIT, see LICENSE.md for details.

About

A type-enforced system for building UI components in React Native with TypeScript.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages