My understanding is that a canonical Rails app (i.e. using Sprockets 4 and ActionView partials) cannot scope CSS to a specific view/partial, instead letting all CSS affect the global scope. Is that correct? Isn’t CSS scoping something that we’d want in Rails or what is the alternative, recommended approach?
That’s the default nature of CSS, which is widely understood. Plenty of approaches out there on how to scope, either logically (eg. BEM / OOCSS) or structurally (eg. CSS Modules). Cost / benefit of each approach depends on the size of your app and the size of your dev team. Don’t think Rails should be prescribing a specific approach.
What would be the cost of having scoped CSS in Rails (if at all feasible) like Svelte or Next.js does out of the box?
This approach requires a pretty complicated frontend toolchain. Most applications don’t need to deal with configuring and debugging that level of complexity if something breaks.
What would be the most “canonical” way to have structurally scoped CSS in a Rails app, for those who do want to give it a try?
You can scope CSS per view, just add
controller_name and action_name to your body tag something like this:
<body class="<%= controller_name %> <%= action_name %>"
Personally I love to use TailwindCSS
One of the ways I’ve been experimenting with at the moment is using native web components with lit-element (not necessary but is a nicer tiny API which sits on top of native web components).
They have this out of the box through what’s called the shadow DOM and it does all of this without all the normal drawbacks associated with scoped styles (I.e randomly renaming all your css classes to incomprehensible strings and then doing a mapping them back to what you actually wrote in the first place).
But overall I would consider it a really nice experience so far (there is a small ramp up if you haven’t done a lot of component based front end stuff previously but it’s infinitely more sane than something like React to me). Imagine it as something between Stimulus and React but is natively built into browsers making it even faster.
‘canonical’ depends on your front end framework of choice. Webpacker has inbuilt support for a few options.
I prefer BEM style for that. So if you want scope something inside projects for example… You can create classes like .project-summary, .project-map. To help me with this approach I created a gem SASS-ZERO
I think that this is a place where there are few different, wildly divergent schools of front-end thought. My personal preference is to lean aggressively into the original spirit of CSS and create safeguards that allow you to use global styles wherever possible. https://every-layout.dev/ is an incredible reference for this approach.
I also think that the appropriate CSS strategy changes a lot based on your team structure and your design team. A lot of the utility-first CSS approaches were first developed at companies like Facebook to avoid merge conflicts. If you’re taking the “global styles wherever possible” approach, at a company of Facebook’s scale, it’s easy for ten different developers to touch the same five lines of CSS in the course of a day. So merge conflicts become a constant and impossible fight. At companies like the ones I usually work at (5 - 50 developers), we just don’t have those problems. Instead, we have massive problems with design consistency & layout edge cases, caused by integrating work our designers are doing now into work our designers did six months ago. So to me, a CSS style that forces you to confront consistency problems instead of letting them fester is preferable.
Because Rails is a big tent, and because the appropriate CSS strategy varies so much based on team size and composition, I’m not sure it’s appropriate for the framework to codify a given set of practices at this point. I think that if people would like a given practice set codified, gems are the appropriate place to do that for now. If some obvious patterns emerge from that set of gems, then they can be incorporated into Rails core. (Just like we went from the free-for-all of Paperclip, Carrierwave, Shrine, Refile, etc to having ActiveStorage, or from converging around Devise to discussions of whether ActiveAuthentication might be an appropriate Rails feature.)
Very well articulated. I think having this kind of reasoning recorded in the official guides would be helpful for devs who are unsure what stack to pick, and are swayed towards best practices (ie. whichever flavor is in season at the moment).
The best way to add specific styles to a view without having to scope it with a body class is to add this to your application.html file
<head> <%= yield :head %> </head>
And then in specific views add stylesheets
<% content_for :head do %> <%= stylesheet_link_tag "users/or_something" %> <% end %>
Thank you all the comments which are very helpful and apologies that I was not clear in my original post, as I do not have a front-end engineering background and lack the vocabulary.
What I really mean to ask is, given Rails’ defaults (in particular: Sprockets for CSS and not Webpack which is not yet the “officially” recommended tool for that), how does one go about implementing “structurally” scoped CSS, i.e. CSS which cannot accidentally leak from one view into others if developers fail to follow the various required conventions? A system that ensures that CSS written in one file can only affect the view that imports or somehow explicitly references that file.
NB: in our prod app we already use classes on body tags and BEM classes, but the question here precisely comes from the fact that there are a number of views where those conventions have not been followed. We are still stuck with Zurb Foundation 5 and I am definitely interested in Tailwind, but I guess it will not solve the question completely as various ad-hoc styles will probably still be needed.
Using the method above you can do exactly that. You can structure your stylesheets directories like your view directories and include them like I showed.
Yes you can’t ensure another file includes that css… But I think if you structure your css directory well it helps.
Thank you @markvanlan, I missed mentioning that in my latest reply. This looks like a sensible solution.