User Theme Switching with SASS - Ruby on Rails

User theme switching with SASS - Ruby on Rails

3 easy steps:

  1. Compile all themes into different files upon deploy. This will take care of timestamping, zipping, etc.

  2. Render page with default theme.

  3. Use javascript to load alternate theme CSS.

No need to mess with dynamic compilation and all that.

To load a CSS dynamically you can use something like this:

function loadCSS(url) {
var cssfile = document.createElement("link");
cssfile.setAttribute("rel", "stylesheet");
cssfile.setAttribute("type", "text/css");
cssfile.setAttribute("href", url);
}

Rails and SASS: Multiple Color Themes Using Same Variables - Allow user to select preferred theme for account

I found a solution that seems to be working well. I can see this method maybe not being practical if you have a bunch of themes that are very large, since it precompiles each theme out. However, I found this to be a great solution for my project. If anyone spots any potential issues I would love to hear them, as I am still a little new to some of this and do not want to break rails rules...

My stylesheets folder looks something like this:

app/assets/stylesheets/

_forms.scss

_layout.scss

_manifest.scss

_mixins.scss

_tables.scss

_utilities.scss

light_theme.scss

dark_theme.scss

I have all my components separated out into partials for organization. The "_manifest.scss" file imports all of my components for my themes.

app/assets/stylesheets/_manifest.scss

// ---------------- Import all CSS components ----------------
@import 'bootstrap';
@import 'utilities';
@import 'layout';
@import 'tables';
@import 'forms';

I make an .scss file for each theme with the color variables at the top THEN import the manifest. Note, use the same variable names so it swaps out easily.

app/assets/stylesheets/light_theme.scss

// ---------------- LIGHT THEME ----------------
// Theme Colors
$light: #f4f3ef;
$medium: #9a9a9a;
$dark: #252422;
$ocean: #36414f;
$accent: cadetblue;
$red: #ed4033;
$orange: #ff8c65;
$yellow: #f4ba58;
$green: #76c29d;
$blue: #65b4c6;

// Import all components
@import 'manifest';

app/assets/stylesheets/dark_theme.scss

// ---------------- DARK THEME ----------------
// Theme Colors
$light: #d5d6fa;
$medium: #9e9ed6;
$dark: #0d1118;
$ocean: #1f2c44;
$accent: #806df3;
$red: #f24e77;
$orange: #f2a23b;
$yellow: #f2c54a;
$green: #34d07e;
$blue: #2c8fef;

// Import all components
@import 'manifest';

I am using bootstrap for my CSS framework, so most of my color-specific css is set through classes like "btn-blue" "text-orange". This way I have less code to sift through and change when I want to switch themes. The text-orange will still be orange, just a brighter orange on the dark theme vs. a pastel orange on the light theme, which is why the simple variable swap was so necessary.

Some elements need specifically called out, so I use a body class for each theme as well. This lets you make theme-specific changes.

app/assets/stylesheets/_layout.scss

body.light_theme {
background-color: $light;
color: $dark;
}

body.dark_theme {
background-color: $lake;
color: $white;
}

Next I wanted my user to be able to set their own theme. I created a migration that added a column to my Users table:

class AddThemeToUsers < ActiveRecord::Migration[5.1]
def change
add_column :users, :user_theme, :string
end
end

On my Users form, they can select their theme via a dropdown. Your select options are important here because the name turns into the variable, so keep naming consistent.

app/views/users/_form.html.erb

<div class="form-group">
<%= form.label :user_theme, 'Theme' %>
<%= form.select :user_theme, ['Light Theme', 'Dark Theme'], class: 'form-control' %>
</div>

Now I set my theme in the application controller. If a user is logged in, it takes their theme setting, otherwise it defaults to Light Theme. If you are adding user_theme row to an existing users table, make sure you run a migration to set a default theme on every user before adding the below or you will get an error because that cell is empty. Or add some code to check if current_user.user_theme is empty then default to another theme. (Probably better...)

application_controller.rb

class ApplicationController < ActionController::Base

before_action :set_theme

private
def set_theme
if current_user
@user_theme = current_user.user_theme.parameterize.underscore
else
@user_theme = 'light_theme'
end
end

end

In the layout file I add the theme to the body as a class:

app/views/layouts/application.html.erb (layout file)

<body class="<%= @user_theme %>">
...
</body>

You may run into another issue, if you get an asset compile error, add this to your config/intitializers/assets.rb one for each theme file.

Rails.application.config.assets.precompile += %w( dark_theme.css )
Rails.application.config.assets.precompile += %w( light_theme.css )

Dynamic styles for different user

It's better to rely on pure CSS features in this one rather than compile your SCSS on each request (which will be very inefficient).

Given this base SCSS file:

.base-theme {
.color-main {
color: $blue-lighter;
}
// and so on
}

You can use this like this:

<p class="color-main"> ... </p>

Based on settings stored in the database you can generate an alternative
theme like so:

<style>
.alt-theme .color-main {
color: <%= current_user.colors.main %>;
}
</style>

and apply them later like so:

<body class="<%= user.has_theme?? 'alt-theme' : 'base-theme'  %>">...</body>

Set Sass variable from controller action in Rails 4

one way to handle: get the user's preference and put it into a

data-user-preference="<%= current_user.pref %>">

then use jquery to add the appropriate class based on that pref

$('.class-name').data('user-preference').addClass('whatever')

can i change a sass variable based on a rails config setting

See: How can I use Ruby/Rails variables inside Sass?

Just add .erb to your sass stylesheet filename. Then use variables as normal:

<%= Rails.application.config.dark-color %> 

Themes in Rails app

The easiest way to theme a site is to simply link to a different stylesheet. You can do this dynamically using something like:

# in app/views/layouts/application.html.erb
<%= stylesheet_link_tag :application %>
<%= stylesheet_link_tag #{current_theme} %>

# in app/helpers/application_helper
def current_theme
# You'll have to implement the logic for a user choosing a theme
# and how to record that in the model.
# Also, come up with a better name for your default theme, like 'twentyeleven' ;)
current_user.theme || 'default'
end

Then you can have a couple manifests for themes. For example, your assets directory can look something like this:

  • app/assets/stylesheets
    • application.css
    • buttons.css
    • theme1/
      • index.css
      • buttons.css
    • theme2/
      • index.css
      • buttons.css

This will get you started with pure css theming. At some point you'll probably want to also them javascript and html layouts. When you start finding the need to do something like this in your html:

<% if current_theme == 'theme1' %>
<li>...
<% elsif current_theme == 'theme2' %>
<b>...
<% end %>

then it's time to implement a more robust theming framework:

  • namespace your html templates by theme (e.g. app/views/themes/theme1/users/index.html.erb) and render the themed version instead of the default
  • namespace just the partials by template (e.g. app/views/themes/theme1/users/_form.html.erb) and add a helper method like render_themed_partial
  • similar to the above approaches, but when the themes get very large, you should consider putting them into their own gems as rails engines

Note: This is all for static themes. For dynamic themes (e.g. where an admin can login and edit the stylesheets or html), you'll have to store theming information in the database. Depending on your architecture, you may be able to provide a set of static themes, and then another theme that dynamically grabs styling data from the database. At that point, you're developing a CMS, however, so it's outside the scope of this answer :)



Related Topics



Leave a reply



Submit