<onWebFocus />

Knowledge is only real when shared.

Prefab-Pattern

August 9, 2021

Pattern for configurable complex components in UI libraries.

Frontend UI libraries have the advantage that you get well styled components out-of-the-box and don't need to style each one each time from scratch. The issue with using a library is often that you cannot customize anything the way you want. This article explores a pattern for exporting components as to allow maximum customization while also making it easy to get started with the default case quickly.

The Goal

The library should export components in a way that most styles can be customized and elements can be placed with as much flexbility as possible. Also, components should display meaningful content with minimal configuration for quick prototyping.

Components should be embeddable into JavaScript (JSX) as well as the styles (CSS-in-JS) to embrace this recent paradigm shift to merge HTML, CSS and JS together into the JavaScript file.

Previous Approaches

Let's take a look at the interface provided by previous UI libraries.

Bootstrap

Arguably the first popular UI library and one to remain popular for a very long time. The framework provides CSS classes that can then be applied to various tags. The schemantics of the arragement of tags is found in the documentation and most users copy and paste it from there making modifications where deemed necessary.

<div class="alert alert-warning alert-dismissible fade show" role="alert">
  <strong>Holy guacamole!</strong> You should check in on some of those fields below.
  <button type="button" class="close" data-dismiss="alert" aria-label="Close">
    <span aria-hidden="true">&times;</span>
  </button>
</div>

Since bootstrap only provides the classes, one can override them easily and adapt the markup and content in any way they like.

Other frameworks working the same way are Materialize or Pure.css. Also worth mentioning is the still very popular Tailwind CSS which has gained popularity only recently and is providing a lot of classes which are very descriptive and can be applied in lots of places. Tailwind is great for people new to Web Development.

Framework-specific libraries

Once frontend frameworks along the lines of Angular, React and Vue came along new types of frameworks finally had a chance to rival the popularity of Bootstrap as they could harness the advantages gained by integration with a specific framework. In this article we are only going to look at frameworks specific to React as it's currently the most popular JavaScript framework. However, the things mentioned as well as the pattern itself also apply to other frameworks.

React: Material-UI and Ant Design

The most popular UI framework for React is @material-ui/core sitting at almost 2 million weekly downloads. This framework follows the Material Design style made popular by Google's Andorid Operating System in 2014. The other one worth mentioning is antd which somewhat resembles Bootstrap.

import Alert from '@mui/material/Alert'
import AlertTitle from '@mui/material/AlertTitle'

const MyAlert = (
  <Alert severity="warning" classes={{ message: 'my-class' }} sx={{ background: 'red' }}>
    <AlertTitle>Warning</AlertTitle>
    This is a warning alert — <strong>check it out!</strong>
  </Alert>
)

Above you see an example of how an alert component is used with Material UI and can be configured. For every component the base styles can be configured by adding an additional class created from object styles or Material UI's own implementation of styled-components. On the component itself additional classes can be added as well as object styles applied to the wrapper component. There are components that should be used together but each component has it's own import location.

CSS-in-JS: Chakra UI

The next leap took place as CSS classes got replaced by writing CSS directly inside JavaScript. This approach is still new and not yet as popular as the previously mentioned ones. With frameworks like @chakra-ui/react we can gain some insight into how such libraries let the user interface with their components.

<Alert status="error">
  <AlertIcon />
  <AlertTitle mr={2}>Your browser is outdated!</AlertTitle>
  <AlertDescription>Your Chakra experience may be degraded.</AlertDescription>
  <CloseButton position="absolute" right="8px" top="8px" />
</Alert>

Similar to previous approaches except components can be styled with the @emotion based css property or extended with the styled api. In addition every component can be styled with a set of descriptive style properties similar to tailwind.

Prefab Components

This pattern aims to bring back the customization known from the class based bootstrap approach where the user can add, customize and remove any components at will. However, with the additional benefit of having to copy less tags and only adding things when something is customized.

const MyHeader = (
  <Header>
    {({ TitleText, Middle, Meta, Navigation }) => (
      <>
        <TitleText>My Page</TitleText>
        <Middle>
          <TextLink>Click me</TextLink>
        </Middle>
        <Meta links={[
          { name: 'Overview', url: 'overview' },
          { name: 'GitHub', url: 'https://github.com/tobua/naven' },
        ]} />
        <Navigation hint="see below for more" />
      </>
    )}
  </Header>
)

The above highlights the pattern on the basis of the Header component. Inside the Header several components can be placed all of whom reside within the Header scope. This way the Header can apply custom logic according to the components passed in. The user can pass any desired components and arrange them.

This pattern was implemented in naven a newly created UI library aiming to make the development of customized websites as quick and easy as possible. The components are all documented separately.

Each component can be styled with a css prop. Usually these styles are applied to the main tag. In case any other tag styles have to be extended the styles prop can be used to style multiple tags.

import { Content, Input } from 'naven'

export default () => (
  <Content>
    <Input placeholder="Input required" required />
    <Input
      css={{ background: 'lightblue' }}
      placeholder="Input"
      value="Lightblue Background"
    />
    <Input
      styles={{
        Cursor: { css: { border: '2px solid red' }, tag: 'div' },
        Input: { css: { color: 'red' } },
      }}
      value="Red Value"
    />
  </Content>
)