ViewComponent for reusable, testable view components

Sarah Mitchell Feb 2026
2 tabs
# Gemfile
gem 'view_component'

# app/components/button_component.rb
class ButtonComponent < ViewComponent::Base
  VARIANTS = %w[primary secondary danger].freeze
  SIZES = %w[small medium large].freeze

  def initialize(variant: 'primary', size: 'medium', type: 'button', **options)
    @variant = variant
    @size = size
    @type = type
    @options = options
  end

  def call
    content_tag :button, content, class: classes, type: @type, **@options
  end

  private

  def classes
    [
      'btn',
      "btn-#{@variant}",
      "btn-#{@size}",
      @options[:class]
    ].compact.join(' ')
  end
end

# app/components/button_component.html.erb
<button class="<%= classes %>" type="<%= @type %>">
  <%= content %>
</button>

# Using in views
<%= render ButtonComponent.new(variant: 'primary', size: 'large') do %>
  Click Me
<% end %>

<%= render ButtonComponent.new(variant: 'danger', type: 'submit') do %>
  Delete Account
<% end %>

# Component with slots
class CardComponent < ViewComponent::Base
  renders_one :header
  renders_one :footer
  renders_many :actions

  def initialize(title: nil)
    @title = title
  end
end

# app/components/card_component.html.erb
<div class="card">
  <% if header? %>
    <div class="card-header">
      <%= header %>
    </div>
  <% end %>

  <div class="card-body">
    <%= content %>
  </div>

  <% if footer? %>
    <div class="card-footer">
      <%= footer %>
    </div>
  <% end %>

  <% if actions? %>
    <div class="card-actions">
      <% actions.each do |action| %>
        <%= action %>
      <% end %>
    </div>
  <% end %>
</div>

# Using card with slots
<%= render CardComponent.new do |card| %>
  <% card.with_header do %>
    <h3>Card Title</h3>
  <% end %>

  <p>Card content here</p>

  <% card.with_footer do %>
    <small>Updated 2 hours ago</small>
  <% end %>

  <% card.with_action do %>
    <%= link_to "Edit", edit_path %>
  <% end %>

  <% card.with_action do %>
    <%= link_to "Delete", delete_path %>
  <% end %>
<% end %>
2 files · ruby Explain with highlit

ViewComponent brings component architecture to Rails views. Components encapsulate markup, logic, and tests in Ruby classes. I use ViewComponents for reusable UI elements—buttons, cards, modals, alerts. Components accept parameters via initializer, keeping views clean. Previews enable visual development—see all component variants without running app. Testing components is fast—unit tests without controllers or integration setup. ViewComponents render faster than partials—compiled to Ruby methods. Slots allow flexible content composition—header, body, footer. Components support variant rendering for different contexts. Understanding when to use components vs. partials improves architecture. ViewComponents are Rails' answer to React components, keeping logic server-side.