Claude Code Plugins

Community-maintained marketplace

Feedback

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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 integration
  • references/style-variants.md - Full Style Variants DSL, compound variants, TailwindMerge