viewcomponent-coder
Build component-based UIs with ViewComponent and view_component-contrib. Use when creating reusable UI components, implementing slots and style variants, or building component previews. Triggers on ViewComponent creation, component patterns, Lookbook previews, or UI component architecture.
$ Installer
git clone https://github.com/majesticlabs-dev/majestic-marketplace /tmp/majestic-marketplace && cp -r /tmp/majestic-marketplace/plugins/majestic-rails/skills/viewcomponent-coder ~/.claude/skills/majestic-marketplace// tip: Run this command in your terminal to install the skill
name: viewcomponent-coder description: Build component-based UIs with ViewComponent and view_component-contrib. Use when creating reusable UI components, implementing slots and style variants, or building component previews. Triggers on ViewComponent creation, component patterns, Lookbook previews, or UI component architecture.
ViewComponent Patterns
Build modern, component-based UIs with ViewComponent using Evil Martians' view_component-contrib patterns.
When to Use This Skill
- Creating ViewComponent classes
- Implementing slots and style variants
- Building Lookbook previews
- Testing components in isolation
- Refactoring partials to components
Core Principle: Components Over Partials
Prefer ViewComponents over partials for reusable UI.
Why ViewComponents?
- Better encapsulation than partials
- Testable in isolation
- Object-oriented approach with explicit contracts
- IDE support and type safety
- Performance benefits (compiled templates)
Setup
# Gemfile
gem "view_component"
gem "view_component-contrib" # Evil Martians patterns
gem "dry-initializer" # Declarative initialization
gem "lookbook" # Component previews
gem "inline_svg" # SVG icons
Install with Rails template:
rails app:template LOCATION="https://railsbytes.com/script/zJosO5"
Base Classes
# app/components/application_view_component.rb
class ApplicationViewComponent < ViewComponentContrib::Base
extend Dry::Initializer
end
# spec/components/previews/application_view_component_preview.rb
class ApplicationViewComponentPreview < ViewComponentContrib::Preview::Base
self.abstract_class = true
end
Basic Component with dry-initializer
# app/components/button_component.rb
class ButtonComponent < ApplicationViewComponent
option :text
option :variant, default: -> { :primary }
option :size, default: -> { :md }
end
<%# app/components/button_component.html.erb %>
<button class="btn btn-<%= variant %> btn-<%= size %>">
<%= text %>
</button>
Style Variants DSL
Replace manual VARIANTS hashes with the Style Variants DSL:
class ButtonComponent < ApplicationViewComponent
include ViewComponentContrib::StyleVariants
option :text
option :color, default: -> { :primary }
option :size, default: -> { :md }
style do
base { %w[font-medium rounded-full] }
variants {
color {
primary { %w[bg-blue-500 text-white] }
secondary { %w[bg-gray-500 text-white] }
danger { %w[bg-red-500 text-white] }
}
size {
sm { "text-sm px-2 py-1" }
md { "text-base px-4 py-2" }
lg { "text-lg px-6 py-3" }
}
}
# Apply when multiple conditions match
compound(size: :lg, color: :primary) { "uppercase" }
defaults { { color: :primary, size: :md } }
end
end
<button class="<%= style(color:, size:) %>">
<%= text %>
</button>
Component with Slots
class CardComponent < ApplicationViewComponent
renders_one :header
renders_one :footer
renders_many :actions
end
<%= render CardComponent.new do |card| %>
<% card.with_header do %>
<h3>Title</h3>
<% end %>
<p>Body content</p>
<% card.with_action do %>
<%= helpers.link_to "Edit", edit_path %>
<% end %>
<% end %>
Important Rules
1. Prefix Rails helpers with helpers.
<%# CORRECT %>
<%= helpers.link_to "Home", root_path %>
<%= helpers.image_tag "logo.png" %>
<%= helpers.inline_svg_tag "icons/user.svg" %>
<%# WRONG - will fail in component context %>
<%= link_to "Home", root_path %>
Exception: t() i18n helper does NOT need prefix:
<%= t('.title') %>
2. SVG Icons as Separate Files
Store SVGs in app/assets/images/icons/ and render with inline_svg gem:
<%= helpers.inline_svg_tag "icons/user.svg", class: "w-5 h-5" %>
Don't inline SVG markup in Ruby code - use separate files instead.
Conditional Rendering
class AlertComponent < ApplicationViewComponent
option :message
option :type, default: -> { :info }
option :dismissible, default: -> { true }
# Skip rendering if no message
def render?
message.present?
end
end
Lookbook Previews
# spec/components/previews/button_component_preview.rb
class ButtonComponentPreview < ApplicationViewComponentPreview
def default
render ButtonComponent.new(text: "Click me")
end
def primary
render ButtonComponent.new(text: "Primary", color: :primary)
end
def all_sizes
render_with(wrapper: :flex_row) do
safe_join([
render(ButtonComponent.new(text: "Small", size: :sm)),
render(ButtonComponent.new(text: "Medium", size: :md)),
render(ButtonComponent.new(text: "Large", size: :lg))
])
end
end
end
Access at: http://localhost:3000/lookbook
Testing Components
RSpec.describe ButtonComponent, type: :component do
it "renders button text" do
render_inline(ButtonComponent.new(text: "Click me"))
expect(page).to have_button("Click me")
end
it "applies style variant classes" do
render_inline(ButtonComponent.new(text: "Save", color: :primary, size: :lg))
expect(page).to have_css("button.bg-blue-500.text-lg")
end
end
Detailed References
For advanced patterns and examples:
references/patterns.md- Slots, collections, polymorphic components, Turbo integrationreferences/style-variants.md- Full Style Variants DSL, compound variants, TailwindMerge
Repository
