ViewComponent + Tailwind: The Omakase Stack Nobody Asked For

DHH talks a lot about the “omakase” approach - the chef’s choice, the defaults that work. But sometimes the chef’s choice needs a little customization.

The Stack

  • Rails 7.1 (the omakase foundation)
  • ViewComponent (for component architecture)
  • Tailwind CSS (for styling)
  • Hotwire (for interactivity)

Nothing fancy. Nothing trendy. Just solid tools that work.

Why ViewComponent?

Because partials get messy. Because helpers are a dumping ground. Because components are just better.

A ViewComponent is just a Ruby class:

class StatCardComponent < ViewComponent::Base
  def initialize(value:, label:, glow: false)
    @value = value
    @label = label
    @glow = glow
  end
end

And a template:

<div class="stat-card">
  <span class="stat-value <%= 'glow' if @glow %>">
    <%= @value %>
  </span>
  <span class="stat-label">
    <%= @label %>
  </span>
</div>

That’s it. No magic. No DSL. Just a class and a template.

Why Tailwind?

Because I got tired of naming things. Because flex items-center gap-4 is clearer than .header-nav-container-flex.

Yes, the classes are verbose. But they’re also:

  • Searchable
  • Self-documenting
  • Easy to modify
  • Impossible to create cascade issues with

The Component Library

After 5 weeks, I had 43 components:

Layout Components:

  • Container
  • Header
  • Footer
  • Sidebar
  • Grid

UI Components:

  • Button (with 6 variants)
  • StatCard
  • Table
  • Form
  • Modal
  • Alert

Interactive Components:

  • Dropdown
  • Tabs
  • Accordion
  • Search
  • Filter

Each one perfectly styled with the terminal aesthetic. Each one tested. Each one documented.

The Pattern

Every component follows the same pattern:

  1. Ruby class - Handles logic and state
  2. ERB template - Handles markup
  3. Tailwind classes - Handles styling
  4. Test - Ensures it works

No magic. No abstractions. Just clear, maintainable code.

What I Learned

ViewComponent is underrated. It gives you the component architecture of React without leaving Rails.

Tailwind is divisive for good reason. You either love it or hate it. I love it because I hate naming things.

The omakase philosophy is a starting point. Take the defaults, then make them yours.

43 components is not too many. Each one has a purpose. Each one sparks joy.

The Alternative

I could have used a pre-built admin framework. Avo. ActiveAdmin. Administrate.

But then I wouldn’t own it. I wouldn’t understand every line. I wouldn’t be able to make it glow green.

The Code

Want to build your own? Start here:

# app/components/base_component.rb
class BaseComponent < ViewComponent::Base
  # Your base styling and behavior
end

# app/components/button_component.rb
class ButtonComponent < BaseComponent
  def initialize(variant: :primary, **options)
    @variant = variant
    @options = options
  end

  def classes
    base = "px-4 py-2 transition-all duration-300"
    variant_class = variant_classes[@variant]
    "#{base} #{variant_class}"
  end

  private

  def variant_classes
    {
      primary: "bg-black border border-green-500 text-green-500 hover:shadow-glow",
      secondary: "bg-black border border-green-700 text-green-700"
    }
  end
end

Then build from there. One component at a time. Make each one yours.

The Result

A component library that:

  • Works exactly how I need it to
  • Looks exactly how I want it to
  • Makes me smile every time I use it

That’s the omakase stack nobody asked for, but everyone should consider.


Commit #492 was when the component library felt “complete”. It wasn’t, of course. It never is.