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.
Build reusable UI components with ViewComponent and Evil Martians' patterns. Use when creating components with slots, style variants, or Lookbook previews. Triggers on ViewComponent creation, refactoring partials to components, or building component-based UIs.
/plugin marketplace add majesticlabs-dev/majestic-marketplace/plugin install majestic-rails@majestic-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/patterns.mdreferences/style-variants.mdBuild modern, component-based UIs with ViewComponent using Evil Martians' view_component-contrib patterns.
Prefer ViewComponents over partials for reusable UI.
# 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"
# 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
# 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>
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>
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 %>
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.
class AlertComponent < ApplicationViewComponent
option :message
option :type, default: -> { :info }
option :dismissible, default: -> { true }
# Skip rendering if no message
def render?
message.present?
end
end
# 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
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
For advanced patterns and examples:
references/patterns.md - Slots, collections, polymorphic components, Turbo integrationreferences/style-variants.md - Full Style Variants DSL, compound variants, TailwindMergeThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.