DEV Community

Cover image for Experimenting with Modern UI Alternatives in Rails
Cristian Molina
Cristian Molina

Posted on

7

Experimenting with Modern UI Alternatives in Rails

I embarked on an experiment with a Rails application using modern alternative tools and libraries. In this post, I'll walk you through the steps I took to set up a Rails template repository with the following technologies:

  • Configuration with Bun JS runtime for efficient JavaScript management.

  • Integration of TailwindCSS for sleek UI design.

  • Utilization of Phlex library to build views entirely in Ruby, eliminating the need for mixed template languages and replacing partials and view helpers with components.

  • Implementation of HTMX actions for dynamic content swapping without full page refresh.

Setup Steps:

  1. Installed bun js runtime (I used mise, btw)

  2. Added necessary gems and generated some configurations:

rails new sample-02_bun_tw_phlex_htmx -j bun -c tailwind -d sqlite3 --skip-hotwire --skip-jbuilder --skip-bootsnap
bin-/bundle add rails-htmx
bin/bundle add phlex-rails
bin/rails generate phlex:install
bin/bundle add tailwindcss-rails
bin/rails tailwindcss:install
bin/rails g phlex:controller Articles index
view raw setup.sh hosted with ❤ by GitHub
  1. Edited Procfile to remove bun css watch line and use tailwindcss:watch task.

  2. Edited application_layout.rb to remove javascript_importmap_tags line.

This is how it looks:

application screen-shot showing some articles and an Add button

Server outputs

The Rails bin/dev command starts the three processes defined in the Procfile file. The Rails server, the Bun js runtime, and the TailwindCSS watcher.

Some code highlights:

class ApplicationLayout < ApplicationView
include Phlex::Rails::Layout
def template(&block)
doctype
html do
head do
title { "Rails demo" }
meta name: "viewport", content: "width=device-width,initial-scale=1"
csp_meta_tag
csrf_meta_tags
javascript_include_tag "application"
stylesheet_link_tag "application"
stylesheet_link_tag "tailwind", "inter-font"
end
body(class: "bg-slate-800", "hx-headers": "{\"X-CSRF-Token\": \"#{helpers.form_authenticity_token}\"}") do
header(class: "bg-white px-6 shadow") {
div(class: "h-16 flex items-center justify-between") {
render HeaderButtonComponent.new(:menu)
h1(class: "text-xl font-bold") { "Rails demo" }
render HeaderButtonComponent.new(:user)
}
}
main(class: "m-4 p-4 bg-slate-400 rounded", &block)
end
footer(class: "bg-white px-4 shadow h-12 flex items-center") {
p(class: "m-2 text-sm font-normal") { "© Cristian Molina - #{Time.zone.now.year}" }
}
end
end
end

The main application layout, in Ruby, has some header and footer definitions and a call to a Ruby block in the tag.

class ArticlesController < ApplicationController
layout -> { ApplicationLayout }
def index
render Articles::IndexView.new
end
def create
render ArticleComponent.new, layout: false
end
end

The controller has an action for listing the Articles and the method to handle the POST to add a new Article.

class Articles::IndexView < ApplicationView
HEAD_STYLE = "p-2 font-mono font-bold font-lg font-bold"
BUTTON_STYLE = "font-mono rounded border-2 border-black bg-indigo-200 p-1 text-sm transition-colors focus-ring-2 focus:ring-indigo-800 hover:bg-indigo-100"
def template
div(class: "h-16 flex items-center justify-between") {
h1(class: HEAD_STYLE) { "Articles index" }
button("hx-post": articles_path, "hx-target": "#article-list", "hx-swap": "afterbegin",
class: BUTTON_STYLE) {
plain "Add new article"
}
}
section(id: "article-list") {
articles.each {|el| render el }
}
end
private
def articles
3.times.map do
ArticleComponent.new
end
end
end
view raw index_view.rb hosted with ❤ by GitHub

The Index view has a title, an Add button, and the list of Articles. In a real app these Articles would probably be passed on initialization from a DB query outside this class.

require "faker"
class ArticleComponent < ApplicationComponent
def template
article(class: "mb-2 bg-blue-600 rounded border-solid border-2 border-black item-article") {
h3(class: "text-left text-slate-200 pl-2 font-semibold") {
i { Faker::Lorem.sentence }
}
p(class: "bg-slate-200 p-4") { Faker::Lorem.paragraph }
}
end
end

The Article component just generates some random data. In a real app it would probably be populated from an Active Record object on initialization.

The full source repository is available here.

By following these steps, I was able to leverage a modern UI frontend for a Rails application in a hopefully more maintainable way.

I plan to continue experimenting with these tools on more advanced topics like forms, websockets & SSE updates, filters, pagination, using JS interactivity, and more complex components.

Happy coding!

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

Top comments (2)

Collapse
 
katafrakt profile image
Paweł Świątkowski

cool stack

Collapse
 
stokry profile image
Stokry

Nice

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs