Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change tailwind-config.js dynamically based on user settings in rails

I have a Rails 6 app set up to use Tailwind CSS with Webpacker similarly to how it's done in this GoRails tutorial.

I want to be able to change the Tailwind defaults dynamically based on the controller and action so that it's very easy for users to "skin" sections of the site by selecting a few options that then dynamically adjust a few of the Tailwind config options. (An example of how this could be used would be users logged into the admin area of the site changing their font family and background color to match their brand.)

I can't just add a stylesheet to the layout based on a conditional because I'd have to override all of the instances where a Tailwind css variable I want to change (like "sans-serif"). That would be a lot of work and brittle to maintain as Tailwind evolves.

It would be ideal if there was a way to dynamically insert choices selected by the user into the Tailwind config file (/javascript/stylesheets/tailwindcss-config.js), but I'm not sure how to do this.

Also is there a better way to do this in Rails when using Tailwind? It seems like there should be some way to use Javascript from the controller to dynamically change the settings in my tailwindcss-config.js (The Tailwind config file is explained here). So, something in that file like this:

theme: {
    fontFamily: {
      display: ['Gilroy', 'sans-serif'],
      body: ['Graphik', 'sans-serif'],
    },

What was a font stack hard-coded as a configuration in Tailwind would become this:

theme: {
    fontFamily: {
      display: DYNAMICALLY INSERTED FONT STACK,
      body: ANOTHER DYNAMICALLY INSERTED FONT STACK,
    },

How would you do this in Rails? I have that Tailwind config file living at /javascript/stylesheets/tailwindcss-config.js. Is this possible to do with Webpack in rails? Is this even the correct approach to take with Rails 6 using Webpacker + Tailwind?

like image 550
Lee McAlilly Avatar asked Apr 29 '20 17:04

Lee McAlilly


People also ask

Where does Tailwind config js go?

By default, Tailwind will look for an optional tailwind.config.js file at the root of your project where you can define any customizations.

Is Tailwind CSS heavy?

The default Tailwind configuration comes with 36.4KB minified and g-zipped. Compared to Bootstrap at 22.1KB , Tailwind is 14.3KB heavier.


1 Answers

I have the feeling that we'd be trying to use a 'buildtime' tool for a 'runtime' operation

To directly inject the variable into tailwindcss config file would imply a rebuild of the actual css served to the user, applying the instructions in tailwind config file to the actual content put in app/javascript/css (assuming the setup used in the mentioned video tutorial).

The operation is carried on by webpack, integrated through the webpacker gem.

IMHO, neither webpack nor tailwind were designed with the purpose of rebuilding the assets at runtime, and, even if I'm definitely aware that a universal machine can do anything ;) I wonder where taking this route would take one, mainly in terms of maintainability.

From this link it seems that triggering a rebuild of webpack on a config change is not straightforward.

Here's a somewhat different path to try:

In the <head> section of the application define css variables (more precisely 'css custom properties') for the settings you want your user to access, which can be set and changed dynamically (from js too)

<style>
  :root{
    --display-font: "<%= display_font_families %>";
    --body-font: "<%= body_font_families %>";
    --link-color: "<%= link_color %>";
  }
</style>

Alternatively you could create app/assets/stylesheets/root.css.erb (the extension is important) and include it in your template before tailwind

Then you should be able to change your tailwindcss config to something like the following:

theme: {
    fontFamily: {
      display: "var(--display-font)",
      body: "var(--body-font)",
    },
    extend: {
      colors: {
        link: "var(--link-color)",
      },
    }

This way we define a dynamic css layout that responds to the value of css variables. The variables and the structure they act on reside on the same logical level, which corresponds to the actual webpage served to the user.

css variables are easily accessible from js, this is one way to have a clean access from rails too


Now let's imagine that the user wants to change the link color (applied to all the links).

In our imaginary settings form, she chooses an arbitrary color (in any css-valid format - the only constraint here is that it must be a valid css value, something you'll need to address with some form of input validation).

We'd likely want

  • a preview feature (client side/js): without reloading the page the user should be able to apply the new settings temporarily to the page. This can be done with a js call that sets the new value for the variable --link-color
// userSelectedColor is the result of a user's choice, 
// say it's "#00FF00"

document.documentElement.style
    .setProperty('--link-color', userSelectedColor);

as soon as this value is changed, all the classes previously created by tailwind, and any rule that make use of the variable, will reflect the change, no need to rebuild the css at all.

Please note that our user is not constrained to an arbitrary subset of the possible values, anything that can be accepted by css is fair game. By assigning to the config parameter a css variable, we actually have instructed tailwindcss to specify it in all its classes as a variable value, which now is under our control through css/js ... We definitely DON'T NEED (nor want) webpack to rebuild the styles

To try to make it clearer, with our color example, in the generated css there will be classes like these - have a look at this link for an explanation of how customizing tailwind theme works

/* GENERATED BY TAILWIND - well, this or something very similar :) */

.text-link {
    color: var(--link-color);
}
.bg-link{
    background-color: var(--link-color);
}
/* .border-link { ... */

clearly the browser needs to know the value of --link-color (we've defined it in the :root section) and the value itself can be any valid css, but what interests us is that it can be changed anytime, automagically propagating the change to every rule using it, it's a css variable ...

  • and we'll want a save feature (server side/rails): when the user clicks on 'save', the new settings should be made persistent (saved in db)

this is plainly accomplished (for example) handling the form submit, saving the new value, which will then be pulled from the db to valorize the css variables on the next render of the page

just my 2 cents :) have fun !

like image 112
giulp Avatar answered Sep 20 '22 05:09

giulp