Programming

Glamorous in NextJS with native elements styling

Enable universal rendering of Glamor(ous) styles, including styling native elements.
April 7, 2018
--
User avatar
Adrian Perez
@blackxored

Glamorous is my favorite CSS-in-JS library for React and React Native. Coupled with styled-system, polished and prop-styles, it brings you the most complete and flexible approach to component styling.

Glamorous

Among the many reasons to prefer Glamorous, is the ability to use JSX props for styling and not having to...

Name all the things

Let's take a look at an example of what I mean:

<Box flex={1} justify="center" align="center">
 {/*...*/}
</Box>

See? I didn't have to name this particular instance of Box, and there's only a couple of style overrides I was interested in, I didn't have to create a new component for it.

Glamorous (technically Glamor, which is based upon) allows many ways to override a component's style in addition to the option above. However, these seem to only work on components created by its component factory, not with normal DOM elements. You can see the difference here:

import React from 'react';
import glamorous from 'glamorous';

const Elements = () => (
  <div>
    <glamorous.H1 css={{ color: 'blue' }}>Hi, I'm blue</glamorous.H1>
    <h1 css={{ color: 'blue' }}>I am NOT (yet)</h1>
  </div>
);

The second example would not work, as this is a "default" element. However, there's a way to make that work if you're interested in having consistency across your project above all.

I've chosen a NextJS app for this example, so we'll look at adding Glamorous and enabling the native element style in order.

Adding Glamorous to a NextJS app

In order to add Glamorous to a NextJS (including being compatible with SSR), we need to add them to our project:

$ yarn add glamorous glamor

Then we need to add the following to pages/_document.js (straight from their example page):

/* eslint-disable react/no-danger */
import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import { renderStatic } from 'glamor/server';

export default class MyDocument extends Document {
  static async getInitialProps({ renderPage }) {
    const page = renderPage();
    const styles = renderStatic(() => page.html || page.errorHtml);
    return { ...page, ...styles };
  }

  constructor(props) {
    super(props);
    const { __NEXT_DATA__, ids } = props;
    if (ids) {
      __NEXT_DATA__.ids = this.props.ids;
    }
  }

  render() {
    return (
      <html lang="en">
        <Head>
          <meta name="viewport" content="initial-scale=1.0, width=device-width" />
          <style dangerouslySetInnerHTML={{ __html: this.props.css }} />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

With this in place, we're able to use Glamorous as we would in any other app, so let's enable native DOM element styling now.

Styling native elements

The reason we can't style native elements (lowercase elements, e.g h1, div, etc), is that the custom props are only enabled for components created by Glamorous component factories, which in turn create Glamor elements.

But as you might remember, JSX is mostly syntactic sugar to React.createElement, and in our example, our <h1>{/*...*/}</h1> would get translated to React.createElement('h1', /*...*/). We can take advantage of that, and just replace the calls.

In our .babelrc, we make a couple of changes, instructing Babel to use Glamor.createElement instead of React.createElement:

{
  "presets": ["next/babel", "@babel/stage-3", "@babel/flow"],
  "plugins": [
    ["@babel/transform-react-jsx", { "pragma": "Glamor.createElement"}],
    "glamor/babel-hoist"
  ]
}

Please notice this is Babel 7, but the process should be very similar for version 6.

We took care of replacing the calls, but Glamor would still need to be in scope, in the same way React should. Otherwise we would to add the following statement to every file:

import Glamor from 'glamor';

But we remember that, in NextJS, importing React in your pages/components is not actually required, and we can achieve something similar for Glamor.

How so? We modify our Webpack config in our next.config.js:

const webpack = require('webpack');

module.exports = {
  webpack: config => {
    config.plugins.push(new webpack.ProvidePlugin({ Glamor: `glamor/react` }));

    return config;
  },
};

And that is all we need! Now we can style native elements, like so:

export default () => <h1 css={{ color: "blue" }}>Hello World!</h1>

screenshot

~ EOF ~

Craftmanship Journey
λ
Software Engineering Blog

Stay in Touch


© 2018 Adrian Perez