It's hard to overstate the importance of Cascading Style Sheets (CSS) for all websites. Since the first CSS standards were published in late 1996, we have come quite far regarding features and ecosystems.
Several frameworks have appeared and proved popular, one of the most recent being Tailwind CSS.
In this post, we'll first examine Tailwind's utility-first approach before diving into how to use it in a Ruby on Rails application. You will see how Tailwind helps you to build excellent websites without the need for custom CSS and long debugging sessions.
Let's get started!
Tailwind CSS: A Utility-First Approach
Most CSS frameworks (Foundation, Bootstrap, or Bulma, for example) provide ready-to-use components such as buttons and form fields, so you can quickly assemble blocks to shape an interface.
Typically, adding a button with Bootstrap looks like this:
<button class="btn btn-primary">My Button</button>
In this example, a simple button is defined and styled by applying the btn
and btn-primary
classes. btn-primary
sets the right color for our use case. Yet, that interface can't fit our needs, so we add a custom CSS stylesheet to customize every component:
<button class="btn btn-primary admin-button">My Button</button>
Tailwind is a "utility-first" concept. Instead of providing ready-to-use components such as buttons, it has low-level utility classes that you can compose to build custom designs. As such, it encourages a more functional approach to styling, where you apply pre-defined classes directly in your HTML. It aims to minimize the need for custom CSS and promotes design consistency through the constraints of the utility classes.
"Utility-first" means that Tailwind provides atomic, single-purpose classes you can combine to construct complex designs.
Let's have a look at some code to compare Tailwind and Bootstrap. First, here is how Tailwind lets us style a simple button:
<button class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded">
My Button
</button>
There are a series of button element classes to configure:
-
Background color
bg-blue-500
: While 'blue' is a pre-picked color, we can set the color shade with the number. The higher the number, the darker the color. -
Background color on hover:
hover:bg-blue-600
. -
Text color
text-white
: No need for a number here, as it's white; there is always a default shade if you don't specify a number, such as with text-red. -
Vertical padding
py-2
: 'p' is padding, 'y' is for the vertical axis, '2' is the spacing value, not in pixels but a scale defined in Tailwind. -
Horizontal padding
px-4
: Same as above, with 'x' for the horizontal axis. -
Rounding corners:
rounded
.
This looks more verbose than the Bootstrap example, but by only adding classes, we can adjust each part of the style. We don't need to create a custom CSS class.
You might not be happy with these colors, but the good news is that you can add custom colors. We will cover that later.
A Word on Scales
CSS is mighty when it comes to spacing (such as margins and padding), and you can work with pixels and rems (root-em, a size relative to the size of the root element). This tends to be difficult, though. Tailwind comes with its own spacing scale that hides complexity while also helping with proportionality.
By default, Tailwind offers values between 0 and 96, with each step proportional to the others. For example, the value 16
has twice as much spacing as 8
. Thanks to this, we don't have to do the math to work with rems or pixels. We can define our preferred values and reuse them throughout our design.
Read more about spacing in Tailwind CSS's documentation.
Setting Up Tailwind in a Ruby on Rails Environment
Ruby on Rails 7.x directly supports Tailwind in its application generator.
$> cd ~/workspace/ && mkdir tailwind-tryout && cd tailwind-tryout
$> rails new -d sqlite3 -c tailwind -T .
We'll skip the test configuration (-T) to avoid adding unnecessary complexity to this article.
Tailwind has a neat feature that generates the CSS file your application needs. Other frameworks require you to include a whole CSS file defining a framework (even the pieces you don't use). In contrast, Tailwind will scan your project and generate a CSS file that contains only the classes your project needs.
You do need to run a little utility to make that happen. In development mode, you can run a watcher daemon that will keep things up to date as you work: bin/rails tailwindcss:watch
.
You can also add the watcher daemon to your Procfile
, then use foreman
or overmind
to start the web
and css
processes:
web: bin/rails server
css: bin/rails tailwindcss:watch
Let's now use it within a simple landing page:
bin/rails generate controller Landing index
We can then head to http://localhost:3000/landing/index.
Dissecting Our Landing Page
Every landing page needs a title. The generator works since we configured our application to use Tailwind as its CSS framework.
# app/views/landing/index.html.erb
<div>
<h1 class="font-bold text-4xl">Landing#index</h1>
<p>Find me in app/views/landing/index.html.erb</p>
</div>
We find something that looks like standard HTML here. We have only two classes for the h1 tag:
-
font-bold
: to control the font weight. -
text-4xl
: to control the font size.
If we change text-4xl
to text-xl
and reload the page, the new style will be automatically applied. Looking at the terminal where Foreman is running, you will see that Tailwind has generated a stylesheet in the background again.
That's how simple it is to integrate Tailwind into a Ruby on Rails application (this relies on the tailwindcss-rails gem).
Configuring Tailwind for Ruby on Rails
You can edit the config/tailwind.config.js
file to adjust Tailwind's settings (e.g., to add additional colors, specify a font to use, adjust spacing, etc).
For example, we could add a "copper" color to our backgrounds and text:
module.exports = {
content: ["./src/**/*.{html,js}"],
theme: {
colors: {
copper: {
100: "#FAD9C1",
200: "#F6C8A4",
300: "#F2B786",
400: "#EEA669",
500: "#E9944C",
600: "#D17F3E",
700: "#B96A31",
800: "#A15524",
900: "#8A4018",
dark: "#8A4018",
},
},
fontFamily: {
serif: ["Times", "serif"],
},
extend: {
spacing: {
"8xl": "108rem",
},
},
},
};
Note that the shades are helpful but can instead be named. If we only need three shades, for example, we can use 'light', 'medium', and 'dark' instead of numbers in our views.
We can then use the shades in our title:
<h1 class="font-bold text-4xl text-copper-200">Landing#index</h1>
<h2 class="font-bold text-xl text-copper-dark">Subtitle</h2>
You can find details about this in the tailwindcss-rails gem's README and also the Tailwind CSS documentation.
Asset Pipeline
We have seen how bin/rails tailwindcss:watch
keeps our stylesheets updated in local development mode. If we need to build the stylesheets just once, we can use bin/rails tailwindcss:build
instead.
For production use, you can rely on bin/rails assets:precompile
to directly call bin/rails tailwindcss:build
.
Learn more about the asset pipeline for Ruby on Rails applications.
Tailwind for Rails in Action
Let's check out a couple of practical uses of Tailwind in some views: a form and a responsive navigation bar.
A Simple Form
Using the Ruby on Rails generator, we create a user
resource:
bin/rails g resource user email:string password:string
bin/rails db:migrate
We can then alter the users_controller.rb
file and create a view for the form.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def def new
@user = User.new
end
end
# app/views/users/new.html.erb
<div>
<h1 class="font-bold text-4xl text-blue-500">Users#new</h1>
<%= form_with model: @user, local: true do |form| %>
<div class="mb-6">
<%= form.label :email, class: "block mb-2 text-sm font-medium text-blue-900" %>
<%= form.text_field :email, class: "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" %>
</div>
<div class="mb-6">
<%= form.label :password, class: "block mb-2 text-sm font-medium text-gray-900" %>
<%= form.password_field :password, class: "bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" %>
</div>
<button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center">Submit</button>
<% end %>
</div>
We style each piece individually, adjusting the text color, background color, borders, padding, margins, etc. There is nothing beyond standard Tailwind here, yet we customize the form to fit our needs.
A Responsive Navigation Bar
We can add conditional breakpoints based on a browser's minimum width using any utility class in Tailwind. For example, the following title will change color depending on the window size:
<h2
class="text-base font-semibold text-gray-900 sm:text-teal-800 lg:text-purple-500"
>
Additional information
</h2>
By default, the color is a dark shade of gray. When a browser window's width is between 640px and 1024px, it's a shade of teal. If a window's width is above 1024px, it's a shade of purple.
As Tailwind can also handle columns, here is an example to showcase how an element's column width can change based on window size:
<div class="sm:col-span-2 md:col-span-3">
<label for="region" class="block text-sm font-medium leading-6 text-gray-900"
>State</label
>
</div>
The label "State" spans two or three columns in this case.
Here, using Tailwind's grid layout utilities, we define a grid that is:
- One column wide by default (
grid-cols-1
) - Six columns wide above 640px width
- Eight columns wide above 768px width
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6 md:grid-cols-8">
</div
Breakpoints and their widths:
-
sm
: 640px -
md
: 768px -
lg
: 1024px -
xl
: 1280px -
2xl
: 1536px
As we've seen, Tailwind simplifies page design and the styling of components.
Tailwind vs. Other Frameworks
Now that we understand how Tailwind can be used, let's review its key differences to other frameworks:
- Utility-based: We compose the style of each element using specific CSS classes, each focusing on different parts of the style.
- Get what we need: We only get the parts we need to ship our website, making for faster load times; that optimizes build time.
- Extensible: We can extend or customize TailwindCSS' defaults through a simple configuration file.
- Easy shading of colors: There's no need to figure out how to make lighter or darker shades of a color to handle hover situations, for example.
- Simple spacing: The hidden and proportional scales simplify spacing.
- Less custom CSS: Since we only assemble classes to style elements, we rely less on custom CSS and can share styles (including complete themes) using HTML files and snippets.
- Ruby on Rails friendly: Thanks to the Tailwind gem, everything is integrated into the layouts and the assets pipeline.
Wrapping Up
As we've seen, Tailwind's utility-first approach is a great fit for Ruby on Rails. We don't need to spend time adjusting Tailwind to fit our needs by adding complex custom configurations or additional custom CSS. As we conceive our views and partials, we can use Tailwind utility classes to shape and style them.
If you want to learn more, you can access many ready-to-use templates and components thanks to Tailwind's vibrant community, and products such as TailwindUI (from Tailwind's creators).
Happy coding!
P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!
Top comments (1)
Great post Thomas, one more person confirming that Tailwind CSS is actually a good thing. Some time ago I used to think that the mark-up code ends up actually bloated by the numerous classes which a component might need, however, seeing it in action shows that utility-first is actually a pro and not a con.