Introducing Material Bread for React and React Native

March 20, 2020

This article part of the Baking Material Bread series that covers creating the React Native Material Bread UI library.

Material Bread is a React Native component library that implements Material Design 2.0 across as many platforms as possible including iOS, Android, Web Browsers, Electron, Vue Native, Expo, Windows, and MacOS. Essentially, Material Bread allows developers to write applications using one of the most popular design systems (Material) in one of the most popular front end frameworks (React) across all platforms. Below are some of the motivations and features of the library.

Documentation

Github Repo

StoryBook Demos

Build iOS, Android, Web, and Desktop Apps with the Same Library

The ability to write your application once and render it on all platforms is the dream of cross-platform development. Using out-of-tree renderers, React Native developers can achieve this dream. This approach is not without drawbacks, however, like slightly degraded performance and increased third-party overhead. But one of the biggest issues with React Native on more platforms is the lack of UI libraries that work 100% on both mobile and web leading to most products built using this technique to use two UI libraries. One for the web and one for mobile.

Material Bread solves this problem by providing components that work across all platforms, which allows you to write your UI once instead of twice. This is especially useful for creating unity between mobile apps and mobile webpages, as their designs are usually identical. For example, I recreated the Dev.to homepage using Material Bread and React Native with 100% code sharing between iOS, Android, Web Browsers, and Electron. Not one UI pattern had to be duplicated to support all those platforms.

Dev To Material 100% code sharing

Platform Patterns

Material Bread also has platform-specific UI patterns built into its components to adhere to platform conventions and functionality. For example, hovering over an element is an important UX/UI pattern for the web, but it is not a pattern found on Android or iOS. Material Bread provides a Hoverable component that works as intended on the web and ignores that functionality for mobile so you can safely use it without writing two different versions.

Cross-Platform By Default

Almost all UI libraries were originally built to support one platform in mind, whether that’s React or React Native. Converting these frameworks to work on more platforms, therefore, is difficult, cumbersome, and will undoubtedly produce breaking changes. Material Bread, on the other hand, was built with full cross-platform support from the ground up, which means every single component works across all platforms out of the box.

Highly Customizable

All apps, even those following a design system, require customization in some form. On the one hand, limiting customization is easier to build and support, but it makes real world use difficult whenever small changes are needed. In some cases, I’ve had to replace a dozen or so components from a library that made customization obscure or impossible. All Material Bread components, on the other hand, are fully customizable with props that allow you to completely override the default library settings.

Override Any Style

For example, you can override the container styles for the Avatar component by passing a style object:

style={[
    {
    width: size,
    height: size,
    borderRadius: size / 2,
    backgroundColor: color ? color : theme.primary.main,
    alignItems: 'center',
    justifyContent: 'center',
    },
    style, < ---- Your styles passed in last to override the above platform styles
]}

Or you can override the text or icon in the Avatar with the contentStyles prop:

 style={[
    {
    color: contentColor ? contentColor : 'white',
    fontSize: contentSize ? contentSize : size / 2,
    },
    contentStyles,
]}

Render Whatever You Want

Most components allow you to pass in any arbitrary node to completely replace a component pattern. For example, the Appbar component has a title prop for defining the Appbar Title. If you pass in a string, the component will wrap your string in a <Text> element and follow Material Design conventions. Imagine, however, that your designer wants to place the App Logo, which is an Image, in that specific position within the Appbar. Most UI libraries, in this case, would throw an error if you passed in anything but a string for the title prop, but Material Bread allows you to pass in whatever you want into the title prop and completely replace the <Text> component.

const titleComponent = typeof title == 'string' || title instanceof String ? (
    <Text
        numberOfLines={
        barType === 'prominent' || barType === 'prominent dense' ? 3 : 1
        }
        style={[styles.pageTitle, titleStyles]}>
        {title}
    </Text>
    ) : (
       title <------- Render Whatever you want
    );

The great benefit of this approach is that you no longer need to completely replace the Appbar component when you need something slightly different. You can change what you want because you have complete control over the component, rather than the library having complete control. The component is just like any other component you’d build yourself and abstract out into a components folder. This also creates a separation, per component, between the library (and Material Design) defaults and what your app actually needs.

To illustrate this further, the ActionItems prop on the Appbar component appears on the far right of the Appbar. These are intended, by Material Design, to be icons or buttons. The prop actionItems, in Material Bread, allows you to pass in an array of objects that contain a name and onPress handler function. This will create clickable icons without going through the trouble of importing and creating your own <IconButton> components, essentially allowing you to follow Material Guidelines without doing much work.

However, if you need something slightly different in that position on the Appbar, then you can pass in whatever you want into the ActionItems array. For example, you can pass in a SearchField components as the first item, pass in an object with just a name for the second, and a custom <IconButton> for the third:

<Appbar
  title={"Page Title"}
  navigation={"menu"}
  actionItems={[
    <Searchfield
      style={{
        marginHorizontal: 24,
        flexGrow: this.state.isSearching ? 1 : 0,
        flexShrink: 0,
      }}
      primary
      value={this.state.value}
      onChangeText={value => this.setState({ value })}
      onFocus={() => this.setState({ isSearching: true })}
      onBlur={() => this.setState({ isSearching: false })}
      onCloseIcon={() => this.setState({ value: "" })}
    />,

    { name: "favorite" },
    <IconButton name="more-vert" size={24} color={"white"} />,
  ]}
  navigation={"close"}
/>

This customization and flexibility allows you to create whatever patterns you want without having to start from scratch.

Build Fully Custom Components From Existing Components

Not only do most components allow you to replace specific prop values with whatever you want, but they also allow you to completely replace all children with whatever you want. For example, the Button component comes with a bunch of props for default Material patterns like icons on either side of the text. But you can replace all of the logic inside the button with whatever you want while keeping the ripple and button styles intact:

<Button
  style={{ height: 100, width: 100, flexDirection: "column", marginRight: 24 }}
  type={"outlined"}
  borderSize={4}
  radius={10}
>
  <Icon name="cloud-upload" size={34} />
  <Text style={{ fontWeight: "600", textAlign: "center" }}>Upload files</Text>
</Button>

Again, this allows you to build custom components without starting from scratch.

Build Custom Components From Primitives

Finally, Material Bread also has a few primitive components like Ripple, Paper, Anchor, Color, Hoverable, and Shadow that allow you to build your own complex Material components.

React Material Design 2.0

Material Bread’s goal is to implement all components from the Material Design 2.0 documentation including advanced components like Appbar Bottom and Backdrop. The goal is to provide both simple and advanced components that work well together rather than require developers to string together a bunch of third-party libraries with questionable support to get all the Material components they need.

Copious Live Demos and Examples

Instead of wondering how certain UI patterns can be made with the library, Material Bread provides tons of demos and examples on a live Storybook site so you can easily copy code for common patterns. Live demos for React Native components are not very common, but they’re immensely useful.

Conclusion

Material Bread is a part of an ongoing project to make writing truly cross-platform applications easier. If you’re interested in contributing please check out the Github Repo or contact me on Twitter.

This article part of the Baking Material Bread series that covers creating the React Native Material Bread UI library.