|
| 1 | +--- |
| 2 | +title: Custom color schemes with Ruby on Rails |
| 3 | +author: Ross Kaffenberger |
| 4 | +layout: article |
| 5 | +description: You can edit the color scheme of this website right in content of this blog post. Play with the controls while we highlight the benefits of Rails, Hotwire, and CSS variables. |
| 6 | +summary: You can edit the color scheme of this website right in content of this blog post. Play with the controls while we highlight the benefits of Rails, Hotwire, and CSS variables. |
| 7 | +# published: |
| 8 | +image: articles/custom-color-schemes-with-ruby-on-rails/rainbows.jpg |
| 9 | +uuid: d99f045b-f3f7-4408-811e-9701b1a13ce8 |
| 10 | +tags: |
| 11 | + - Rails |
| 12 | +--- |
| 13 | + |
| 14 | +This blog post lets you edit the color scheme of this site, [Joy of Rails](/). |
| 15 | + |
| 16 | +Give it a try. |
| 17 | + |
| 18 | +<%= turbo_frame_tag "color-scheme-form", src: settings_color_scheme_path(custom_color_scheme_params), class: "grid-cols-12 lg:grid-cols-12" %> |
| 19 | +<noscript> |
| 20 | +JavaScript not enabled? Go to the <%= link_to "color scheme demo", settings_color_scheme_path(custom_color_scheme_params) %>. Then come back when you’re done. |
| 21 | +</noscript> |
| 22 | + |
| 23 | +--- |
| 24 | + |
| 25 | +## How it works |
| 26 | + |
| 27 | +Pretty cool, huh? Here are the key ingredients: |
| 28 | + |
| 29 | +- [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties): Also known as CSS custom properties |
| 30 | +- [SQLite](https://www.sqlite.org/): A `color_schemes` table to store curated color schemes |
| 31 | +- [Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies): A session cookie to store your saved color scheme selection |
| 32 | +- [Hotwire](https://hotwired.dev/): Server-rendered HTML and a single-page app experience powered by Rails, [Turbo Drive](https://turbo.hotwired.dev/handbook/introduction#turbo-drive%3A-navigate-within-a-persistent-process), and [Turbo Frames](https://turbo.hotwired.dev/handbook/introduction#turbo-frames%3A-decompose-complex-pages) |
| 33 | + |
| 34 | +I started with the premise of using a monochromatic color scheme based on the 11 color scale Tailwind uses for each of [its default color sets](https://tailwindcss.com/docs/customizing-colors). The curated options are all generated from [uicolors.app](https://uicolors.app/create). |
| 35 | + |
| 36 | +Each color scheme is a row in the `color_schemes` table, with a name and CSS hex code values for each of the eleven weights. |
| 37 | + |
| 38 | +```ruby:{"filename":"db/migrate/<timestamp>_create_color_schemes.rb"} |
| 39 | +class CreateColorSchemes < ActiveRecord::Migration[7.1] |
| 40 | + def change |
| 41 | + create_table :color_schemes do |t| |
| 42 | + t.string :name, null: false, index: {unique: true} |
| 43 | + t.string :weight_50, null: false |
| 44 | + t.string :weight_100, null: false |
| 45 | + t.string :weight_200, null: false |
| 46 | + # ... and so on |
| 47 | + end |
| 48 | + end |
| 49 | +end |
| 50 | +``` |
| 51 | + |
| 52 | +CSS variables make it easy to change repeated CSS in a lot of places. You can set a CSS variable with double dashes, `--`. The CSS variable can be accessed using the `var()` expression. CSS variables can be overridden and can be defined in terms of other variables. |
| 53 | + |
| 54 | +Here’s a simplified a view of how I used CSS variables to define the main background color of the `<body>` element. |
| 55 | + |
| 56 | +```css:{"show_header": false} |
| 57 | +:root { |
| 58 | + --theme-color-50: var(--my-color-50, var(--default-color-50)); |
| 59 | + --theme-color-100: var(--my-color-100, var(--default-color-100)); |
| 60 | + --theme-color-200: var(--my-color-200, var(--default-color-200));h |
| 61 | + /* and so on */ |
| 62 | +} |
| 63 | + |
| 64 | +body { |
| 65 | + background-color: var(--theme-color-50); |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +Even though Joy of Rails does not use Tailwind, this approach is consistent with [the Tailwind docs for using CSS variables to customize Tailwind colors](https://tailwindcss.com/docs/customizing-colors#using-css-variables). |
| 70 | + |
| 71 | +When you click the "Save" button, the application stores the `id` of the chosen color scheme in your Rails session: |
| 72 | + |
| 73 | +```ruby:{"show_header":false} |
| 74 | +session[:color_scheme_id] = @color_scheme.id |
| 75 | +``` |
| 76 | + |
| 77 | +When the page is rendered, the application checks for the presence of the session data to query for the desired color scheme: |
| 78 | + |
| 79 | +```ruby:{"show_header":false} |
| 80 | +if session[:color_scheme_id] |
| 81 | + @color_scheme = ColorScheme.find(session[:color_scheme_id]) |
| 82 | +end |
| 83 | +``` |
| 84 | + |
| 85 | +The color scheme CSS variables can be written directly into a style tag |
| 86 | + |
| 87 | +```erb:{"filename":"app/view/layouts/application.html.erb"} |
| 88 | +<style> |
| 89 | + :root { |
| 90 | + --my-color-50: <%= @color_scheme.weight_50.to_json %>; |
| 91 | + --my-color-100: <%= @color_scheme.weight_100.to_json %>; |
| 92 | + --my-color-200: <%= @color_scheme.weight_200.to_json %>; |
| 93 | + /* and so on */ |
| 94 | + } |
| 95 | +</style> |
| 96 | +``` |
| 97 | + |
| 98 | +Most of the time-consuming work required to assemble this post was in researching the color palettes and building the site theme with various background and text colors defined in CSS variables. |
| 99 | + |
| 100 | +## Progressively enhanced |
| 101 | + |
| 102 | +Did you notice how the color choice updated automatically in place? At least, it should have for visitors that have JavaScript enabled. |
| 103 | + |
| 104 | +<noscript> |
| 105 | + <p> |
| 106 | + <em>I can see you don’t have JavaScript enabled! That’s okay! The color schemes preview should have at still worked, which might not have been true had I used a JavaScript framework that relies on client-side rendering.</em> |
| 107 | + </p> |
| 108 | +</noscript> |
| 109 | + |
| 110 | +One of the things I love about Rails and Hotwire is that it’s possible to adopt **progressive enhancement**. |
| 111 | + |
| 112 | +> Progressive enhancement is a design philosophy that provides a baseline of essential content and functionality to as many users as possible, while delivering the best possible experience only to users of the most modern browsers that can run all the required code. |
| 113 | +> |
| 114 | +> [Source: MDN Glossary](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement) |
| 115 | + |
| 116 | +In this case, visitors who don’t have JavaScript enabled would see a link to the the <%= link_to "color schemes settings page", settings_color_scheme_path %>. That page has most of the functionality you see here. |
| 117 | + |
| 118 | +This design is made possible by the fact that the color scheme preview HTML is all rendered on the server with Ruby on Rails. With JavaScript enabled, Turbo JavaScript "upgrades" the default browser form submissions and link clicks to AJAX requests and use the HTML response to replace the parts of the page in place. Without JavaScript, visitors will still get the desired results after a full page navigation and/or redirects. |
| 119 | + |
| 120 | +Progressive enhancement hasn’t necessarily been a hot topic alongside the popular JavaScript frameworks that rely on client-side rendering. It’s nice to see that most of what you see on these pages is static content and shouldn’t require a lot of code running in your browser to view. |
| 121 | + |
| 122 | +## Look at all the things I’m not doing |
| 123 | + |
| 124 | +Notice how I do not mention any of the major JavaScript frameworks in the list of tools I used to build custom color schemes. |
| 125 | + |
| 126 | +In fact, I wrote only a one line of custom JavaScript to make the color scheme preview selection work: on an `onchange` handler: |
| 127 | + |
| 128 | +```html:{"show_header": false} |
| 129 | +<select onchange="this.form.requestSubmit()"> |
| 130 | + <!-- options --> |
| 131 | +</select> |
| 132 | +``` |
| 133 | + |
| 134 | +This isn’t to say I don’t like writing JavaScript or that I don’t think you should use JavaScript. I’m not here to tell you that you shouldn’t use React. Or Vue, Svelte, Solid, or Angular. Personally, I think JavaScript and the popular JavaScript frameworks are great. I recognize why and how they have become so popular. |
| 135 | + |
| 136 | +I’m also not here to tell you that Rails with Hotwire is objectively better than any of these JavaScript frameworks or similar options. |
| 137 | + |
| 138 | +But—and this is the important point—I didn’t _need_ any client-side rendering or JavaScript component library to make this work. It _is_ possible to build a single-page application experience in Rails. It’s not always necessary to build a single-page application in JavaScript. |
| 139 | + |
| 140 | +For a solo developer with a busy home life and a full-time job, saving time and energy for other important tasks, like writing, with a language and framework I love; this makes all the difference. |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +Would you like to see an in-depth tutorial on how to build the custom color scheme functionality? Did you find a bug or do you have questions about the content? Please [send me an email](mailto: [email protected]). You can also connect with me on [Twitter](https://twitter.com/rossta), [Github](https://github.com/rossta), [Mastodon](https://ruby.social/@rossta), and [Linkedin](https://www.linkedin.com/in/rosskaffenberger). |
| 145 | + |
| 146 | +Curious to peek behind the curtain and get a glimpse of the magic? [Joy of Rails is open source on Github](https://github.com/joyofrails/joyofrails.com). Feel free to look through the code and contribute. |
| 147 | + |
| 148 | +And if I’ve captured your interest, please <%= link_to "subscribe", "#newsletter-signup" %> to hear more from me and get notified of new articles by email. |
| 149 | + |
| 150 | +That does it for another glimpse into what’s possible with Ruby on Rails. I hope you enjoyed it. |
| 151 | + |
| 152 | +') |
0 commit comments