19

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?

giulp
  • 2,200
  • 20
  • 26
Lee McAlilly
  • 9,084
  • 12
  • 60
  • 94
  • Instead of dynamically changing the variable in the tailwind.config.js file, why not dynamically change the class name? Assuming you are using vanilla js, try [this](https://stackoverflow.com/questions/22576927/how-to-dynamically-change-css-class-of-div-tag) – Ibraheem Ahmed Apr 30 '20 at 22:42
  • The power of Tailwind is that by changing the defaults you get these applied throughout the stylesheet consistently. So you are building from a Design System. If you start overriding individual classes you are back to writing totally one-off CSS and will ultimately need to use something like Tailwind or develop your own CSS design system to make it maintainable. I want users to be able to set system wide variables like header font and link colors, similar to how you can do this in something like Squarespace, not override the individual class names in the html. Does that make sense? – Lee McAlilly Apr 30 '20 at 22:47
  • 2
    I want to do the exact same thing, but I haven't gotten to that part of my app yet. I 'll try to post here again when I get there. Actually I even want to be able to have multiple users, each being able to make their own custom override to the default values. – Tashows Jun 18 '20 at 12:23
  • Tashows. Would def appreciate you sharing the direction you take on this! I also want users to be able to set up their own defaults to "skin" their profile, for instance. – Lee McAlilly Jun 18 '20 at 21:00

3 Answers3

59

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 !

giulp
  • 2,200
  • 20
  • 26
  • Would it be possible to just inject the variable directly into tailwindcss config file with Ruby or Javascript? I don't understand why this would need to be put into the view first? – Lee McAlilly Aug 18 '20 at 15:27
  • That explanation of 'buildtime' tool for a 'runtime' operation helped clarify the problem for me, but your example still would require for the user to pick from pre-defined styles that are already set in the stylesheet and then are toggled in the view based on the approach described above. What I'd really like to do is allow them to use any Hex value for instance, for all links. It would be nice if that could dynamically update in one place (the tailwind config). I guess the best approach is to simply use Ruby to dynamically update a stylesheet the new color then webpack recompiles everything? – Lee McAlilly Aug 19 '20 at 17:43
  • answer updated again - no need to rebuild the styles :) – giulp Aug 20 '20 at 05:32
  • dude that's awesome. totally helps. thank you for such a detailed answer! – Lee McAlilly Aug 20 '20 at 17:16
  • Great answer! thanks so much – Lucas Mar 14 '22 at 13:46
  • Thank you for your kind words, much appreciated. – giulp Mar 15 '22 at 16:44
0

As tailwind config file is "js" you can call ajax data then can add it in the config file data to make it dynamic. As we import the theme file in taiwlind config

  • I thought about this option, but I was wondering how caching works because the asset pipeline has to recompile all of the CSS. Tailwind is served through webpack in rails now. – Lee McAlilly Aug 18 '20 at 15:23
  • One last option is, you can create all the classes you want in config file, then can use it dynamically. I have done this in my recent project. – user297452 Aug 20 '20 at 03:12
-2

I was having the same issue today. This worked for me.

# tailwind.config.js
theme: {
fontFamily: {
  custom: ['Gilroy', 'sans-serif']
},

and use it like

#sample.html.erb
<span class="font-custom"> Hello Tailwind! </span>
  • 2
    Thanks, I was more asking about how to dynamically change the font stack that is listed in tailwind.config.js => `['Gilroy', 'sans-serif']`. I want the user to be able to dynamically swap out say a color that is used in the UI. The easiest way to do this in Tailwind is to change the variable in the tailwind.config.js but how do I do this dynamically? – Lee McAlilly Apr 30 '20 at 16:20