Using and Customizing Ant Design with Rails 6

Ant Design is an incredibly thorough design system with a lot of useful React components. The trick is that all of the CSS is generated with Less and there are no plans to support Sass or anything else. Less support is really poor in the Rails community right now; the gems are largely abandoned. If you want to customize Ant Design with a Rails project you need to do it via Webpack.

Here’s how I was able to get it working after a lot of trial and error and painful web searching (seriously, “Less” is a terrible name for a project).

First I started a new rails project.

$ rails new nessman --database=postgresql
$ cd nessman
$ rails db:migrate

Next I added the react-rails gem. This could have been done with a --webpack=react on creation, but I like to do things manually when I’m learning and it allowed me to switch to the latest webpacker at the same time.

Gemfile
1
2
3
4
5
...
gem 'webpacker', '~> 5.1', '>= 5.1.1' # <-- updated to latest (as of this writing)
gem 'react-rails', '~> 2.6', '>= 2.6.1' # <-- added
...

I used the setup scripts to configure it for my Rails app.

$ bundle install
$ rails webpacker:install
$ rails webpacker:install:react
$ rails generate react:install

I added the JavaScript modules that I would need. I didn’t technically need to add webpack, but it was nice to stop some warnings.

$ yarn add webpack
$ yarn add antd
$ yarn add antd-dayjs-webpack-plugin
$ yarn add less
$ yarn add less-loader
$ yarn add babel-plugin-import

Now that everything was installed, I needed to configure everything. I started by writing a configuration file for less-loader.

config/webpack/loaders/less.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const getStyleRule = require('@rails/webpacker/package/utils/get_style_rule')

module.exports = getStyleRule(/\.less$/i, false, [
  {
    loader: 'less-loader',
    options: {
      sourceMap: true,
      lessOptions: {
        javascriptEnabled: true,
      }
    }
  }
])

I updated the webpack environment to use my loader configuration file.

config/webpack/environment.js
1
2
3
4
5
const { environment } = require('@rails/webpacker')
const less = require('./loaders/less')
environment.loaders.append('style', less)
module.exports = environment

I edited webpacker’s configuration file to reflect these changes.

config/webpacker.yml
1
2
3
4
5
6
...
  extensions:
    ...
    - .less
    - .module.less

I edited Babel’s configuration to import the styles from Ant Design.

babel.config.js
1
2
3
4
5
6
7
8
9
10
11
12
...
  return {
    ...
    plugins: [
      ...
      ],
      [
        "import", { "libraryName": "antd", "libraryDirectory": "es", "style": true }
      ]
    ].filter(Boolean)
  ...

I decided to create a component to ensure that everything was working properly.

$ rails generate react:component ComponentTest

I based it on the first examples from the Ant Design documents.

app/javascript/components/ComponentTest.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react"
import { Button, DatePicker, Switch, TimePicker, version } from "antd";

const ComponentTest = (props) => {
  return (
    <React.Fragment>
      <h1>Ant Design version: {version}</h1>
      <div>
        <DatePicker />
        <Button type="primary" style={{ marginLeft: 8 }}>
          Primary Button
        </Button>
      </div>
      <div style={{ marginTop: 16 }}>
        <TimePicker size="large" />
        <Switch checkedChildren={ props.checked } unCheckedChildren={ props.unchecked } style={{ marginLeft: 8 }} />
      </div>
    </React.Fragment>
  )
}

export default ComponentTest

I created a view to try it out.

$ rails g controller Welcome index

I made that view the default.

config/routes.rb
1
2
3
4
Rails.application.routes.draw do
  root 'welcome#index'
end

I edited the application layout to generate a stylesheet with stylesheet_pack_tag. This is for the styles from my JavaScript.

app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
  <head>
    <title>Nessman</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

I edited the view to render my new component.

app/views/welcome/index.erb
1
2
3
4
<div style="width: 480px; margin: 10px auto;">
  <%= react_component('ComponentTest', { checked: 1, unchecked: 0 }) %>
</div>

It worked well, but I wanted to test one last thing. I edited my Less loader to modify a variable in the design, changing the primary color to a bright red. Any of the default variables can be changed.

config/webpack/loaders/less.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const getStyleRule = require('@rails/webpacker/package/utils/get_style_rule')
const overides = { '@primary-color': '#F00' }

module.exports = getStyleRule(/\.less$/i, false, [
  {
    loader: 'less-loader',
    options: {
      sourceMap: true,
      lessOptions: {
        javascriptEnabled: true,
        modifyVars: overrides
      }
    }
  }
])

I put a working demo up on Heroku.