Ext JS to React: Application Styling

   JavaScript
Ext JS to React: Application Styling

This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.

Styling a React application will be familiar to Ext JS developers. Ext JS shipped with a number of predefined themes with variables enabling customization of each theme. If your React application uses views from a library such as Kendo UI or Semantic, you’ll likely use their tools and processes to refine any theme shipped with their views. However, when it comes to styling custom view components, you’ll own the styling of the components.

Having a theme provided to style all views within an application is certainly a convenience. However, most employers/clients will want a little (or a lot) of customization when it comes to look and feel. That could include colors used, fonts and font sizes, borders and shadows, and more that may depart from a shipped theme. So, whether the task is customizing a shipped theme or styling custom components from the ground up, you’ll likely need a strategy for managing view styles across your application.

In this blog article, we’ll discuss a few common strategies used by those in the React community such as CSS modules, inline styles, and styled-components.

Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.

Simple Styling

The starter app uses an import statement to connect the stylesheet associated with the App component:

import './App.css';

Webpack reads these imports in during development or production builds. This pairing of CSS styling to each corresponding component improves file / component management versus using a single stylesheet to style an entire application and all of its components. If an application is small and doesn’t include components and their accompanying styles shared across multiple projects, this method of importing stylesheets may suffice. But, if the application is sufficiently large or employs shared views, further encapsulating the style rules associated with each view may be crucial. And for that, we’ll look at CSS Modules.

CSS Modules overview

CSS Modules loads CSS stylesheets in addition to pre-processed languages like Sass, Less, and PostCSS. This is similar to how JavaScript modules are loaded. A primary benefit of CSS Modules is that each CSS class is uniquely named within the build process, thus scoping the stylesheet to its paired React component. This unique naming mechanism completely insulates a component’ styles from all other styles in an application. CSS name collisions are effectively eliminated making atomically designed components easier to safely drop in to various applications or modules.

Up to this point we’ve been building our starter app using create-react-app and all of the configuration magic has been handled behind the scenes. But (at the time of this writing) there is no official way to enable CSS Modules without performing an eject on our starter app which takes create-react-app out of the config file driver’s seat and opens up the config options for us to tinker with. Ejecting is a one-time thing per application and there’s no going back to the days of create-react-app managing everything for us. After ejecting, the application is no longer eligible for automatic updates via create-react-app. We are now able to configure webpack per the specifics of our app.

In addition to encapsulating our components we’re going to look at how an application can be configured to work with a CSS preprocessor (in our case Sass, since that’s what we used when theming with Ext JS). As you build custom views and their associated styling you’ll have to decide how many dependencies you want to introduce, such as relying on application-wide variables and links between stylesheets. For the sake of covering this topic we’ll assume that we’re ok with some dependencies within a collection of views all meant to operate with the same theme constants.

CSS Modules setup

In this section we’re going to set up our application for CSS Modules. Follow along with a newly created app as we demonstrate how to configure an application for CSS Modules and Sass. Within the terminal from your application folder run:

npm run eject
npm i sass-loader node-sass-chokidar --save

At this stage, we’ve ejected from the initial starter app and installed two modules used to work with Sass files. sass-loader is an addon to webpack that adds the ability to preprocess Sass files before feeding them to the CSS loader. node-sass-chokidar is an npm module that bundles node-sass and does all the hard work of preprocessing. Chokidar is a highly performant watcher that will ensure low overhead on a development machine.

The eject action exposes the Webpack config files we’ll need to modify in order to set up CSS Modules (Sass Modules). The config files we’ll need to edit are too large to reproduce here in their entirety so we’ll cover updating just the necessary bits. Start by opening {appRoot}/config/webpack.config.dev.js and modifying it to look like:

{
  test: /\.(scss|css)$/,
  use: [
    require.resolve('style-loader'),
    {
      loader: require.resolve('css-loader'),
      options: {
        importLoaders: 2,
        modules: true,
        localIdentName: "[name]__[local]___[hash:base64:5]"
      },
    },
    {
      loader: require.resolve('postcss-loader'),
      options: {
        // Necessary for external CSS imports to work
        // https://github.com/facebookincubator/create-react-app/issues/2677
        ident: 'postcss',
        plugins: () => [
          require('postcss-flexbugs-fixes'),
          autoprefixer({
            browsers: [
              '>1%',
              'last 4 versions',
              'Firefox ESR',
              'not ie < 9', // React doesn't support IE8 anyway
            ],
            flexbox: 'no-2009',
          }),
        ],
      },
    },
    require.resolve('sass-loader')
  ],
},

Note: Be sure to add the same updates/additions to the production Webpack config file at {appRoot}/configs/webpack.config.prod.js taking care to preserve other configuration options present in the production config file that differ from the dev config file.

Finally, run:

npm install
npm start

Defining application styles

At this stage, we are able to begin adding application-wide styling that our individual views can use. Let’s start by adding an {appRoot}/src/styles folder that will hold our global style definitions used by all elements within the application. Add the file {appRoot}/src/styles/styles.scss and add a global base and accent color:

$base-color: #7986cb;
$accent-color: #f48fb1;

Next, add {appRoot}/src/styles/button.scss for our common button style and add the following to the button.scss file:

@import './styles.scss';

.button {
    background-color: $base-color;
    line-height: 32px;
}

Next, let’s add a theme folder with styles that build on any base styling we’ve defined. Start by adding the folder and sub-folder {appRoot}/src/styles/themes/material. Then add a theme-level styles file {appRoot}/src/styles/themes/material/styles.scss. For now, we’ll just import the global styles. In the future, global overrides for the theme can live in this file:

@import './styles.scss';

Now, let’s create a button stylesheet for our material theme. Add the file {appRoot}/src/styles/themes/material/button.scss and update it with the following style rules:

@import '../../button.scss';

.button {
    @extend .button;
    border-width: 0;
    font-family: Roboto, sans-serif;
    text-transform: uppercase;
    font-weight: 500;
    font-size: 14px;
    transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
    border-radius: 2px;
    background-color: transparent;
}
.button:hover {
    background-color: rgba(210, 210, 210, 0.33);
}

We extended the .button rule from the imported button.scss file overriding any base styling as needed for our material button.

Next, we’ll add a Button class at {appRoot}/src/Button.js that will use our custom button styling:

import React from 'react';
import btnStyles from 'button.scss';

export default ({ children, className }) => (
  <button className={btnStyles[className || 'button']}>{children}</button>
);

Finally, let’s modify {appRoot}/src/App.js to show a Button:

import React from 'react';
import Button from './Button';
import './App.css';

export default () => (
  <div>
    <Button className="button">Button</Button>
  </div>
);

After saving all updates, you’ll notice that we’re getting an error saying that the “button.scss” module can’t be found. At this point the only way to load a module would be to link to it explicitly with absolute or relative pathing, something we don’t have to do when importing JavaScript modules. Let’s fix it so that all .css and .scss imports are sourced from the material theme for this application. First, press “ctrl-c” to stop the server spun up by “npm start”. In {AppRoot}/configs/webpack.config.dev.js add the following constant:

const stylePath = path.resolve(__dirname, '../src/styles/themes/material');

Change the resolve.modules entry from:

modules: ['node_modules', paths.appNodeModules].concat(
  // It is guaranteed to exist because we tweak it in `env.js`
  process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),

To

modules: ['node_modules', paths.appNodeModules].concat(
  // It is guaranteed to exist because we tweak it in `env.js`
  process.env.NODE_PATH.split(path.delimiter).filter(Boolean),
  stylePath
),

Now, all of our CSS / Sass modules will source from the material theme folder. While we’re at it, let’s modify the webpack config file so that our Sass “@import” statements will not be required to use explicit pathing as well. Modify the “sass-loader” entry changing it from:

require.resolve('sass-loader')

To

{
  loader: require.resolve('sass-loader'),
  options: {
    includePaths: [stylePath]
  }
}

Note: Be sure to modify {appRoot}/configs/webpack.config.prod.js at this time as well.

Now re-run npm run start to see our changes take shape.

Ext JS to React: Application Styling, Styled Button



You can review the sample code on the Git repo.

Theming React Applications with CSS Modules

The theme styling can now easily be used to style our views. But what if we wanted an arbitrary element in a component to be able to take advantage of a theme’s global variables? Let’s demonstrate this by renaming the {appRoot}/src/App.css file to {appRoot}/src/App.scss and by changing the import statement in {appRoot}/src/App.js to source from App.scss. We’ll replace the contents of {appRoot}/src/App.scss with:

@import 'styles.scss';

.myContainer {
    padding: 8px;
    background-color: $base-color;
}

Here we import the global style rules for the theme including any global variables. We’ll use the $base-color variable in a style rule of .myContainer. Now, let’s update {appRoot}/src/App.js to use the new style rule:

import React from 'react';
import Button from './Button';
import styles from './App.scss';

export default () => (
  <div className={styles.myContainer}>
    <Button className="button">Button</Button>
  </div>
);

Our Button from before is now wrapped in a containing div with a background color dictated by our theme. Additionally, we can create one-off Button ui by adding a rule in our src/App.scss file that composes from our theme’s .button style and overrides select styles. First, let’s add another button to our App component:

import React from 'react';
import Button from './Button';
import styles from './App.scss';

export default () => (
  <div className={styles.myContainer}>
    <Button className="button">Button</Button>
    <Button ui={styles.myButton}>Button</Button>
  </div>
);

Next, we’ll add a .myButton style rule that uses the global $accent-color plus the theme’s button styling. Add the following to the end of src/App.scss:

.myButton {
    composes: button from 'button.scss';
    background-color: $accent-color;
}

.myButton:hover {
    background-color: darken($accent-color, 10%);
}


Ext JS to React: Application Styling, Styled Buttons



We now have a standard themed button followed by one with a pink background color. If this were a commonly needed UI we could add the style rule to the theme for shared use throughout the application.

Now that we’ve created a structure we can use for components going forward we could add another theme folder and create the needed component stylesheets there with whatever nuanced color, layout, or typography changes we want for the new theme. And since our component files and their accompanying stylesheets point to the module names we can change the theme folder we want to use by updating the stylePath constant in the webpack config files. For example, we could create a “fabric” theme by adding an {appRoot}/src/styles/themes/fabric folder and its component stylesheets and updating the webpack configs by changing from:

const stylePath = path.resolve(__dirname, '../src/styles/themes/material');

to

const stylePath = path.resolve(__dirname, '../src/styles/themes/fabric');

You can review the sample code on the Git repo.

Conclusion

Enabling the module-loading behavior with our CSS/Sass files required a little bit of customization to the configuration used by our starter app. The upside, though, is that it’s a small set of changes and only needs to be done the one time. With an application now set up to work with Sass module files you can create global styling for the application with view-specific style rules all with the confidence that all styling is completely encapsulated within each view. When creating a library of views with coordinating style modules to be shared across multiple projects, only the views and their styles imported by a given project will be output in the build process. Your build files will now be as lean as possible with a tidy, maintainable code base.


Like What You See?

Got any questions?