smithery/Kaakati

ViewComponents Specialist

Expert patterns for ViewComponent implementation, slots, previews, and method exposure.

Installation

$ npx skills add smithery/Kaakati --skill viewcomponents-specialist

Summary

  • Expert patterns for ViewComponent implementation, slots, previews, and method exposure.
  • Use when: (1) Creating ViewComponents, (2) Implementing slots or content blocks, (3) Setting up component previews, (4) Debugging template/rendering errors, (5) Exposing service methods to views.
  • Trigger keywords: ViewComponent, components, UI, rendering, slots, previews, partials, presenters, render_inline, erb

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/Kaakati · top by installs.

npx skills add smithery/Kaakati

Browse all from smithery/Kaakati

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.1.0

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,418 B
  • docs SUMMARY.md 434 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

ViewComponents Specialist

Expert patterns for building ViewComponents in Rails applications.

Critical Rule: Method Exposure

Service has method + Component EXPOSES it = View can call it

Views cannot reach through components to access service internals. Every method a view calls must be public on the component.

Pre-Work Protocol (MANDATORY)

Before ANY component work:

# 1. Determine template pattern
head -50 $(find app/components -name '*_component.rb' | head -1) 2>/dev/null
grep -l 'def call' app/components/**/*_component.rb 2>/dev/null | head -3

# 2. Check helper usage pattern
grep -r 'helpers\.' app/components/ --include='*.rb' | head -3

# 3. Check delegation patterns
grep -r 'delegate' app/components/ --include='*.rb' | head -3

Component Creation Decision Tree

What type of component?
│
├─ Simple display (badge, icon)
│   └─ Basic Component pattern
│
├─ Wraps a service/data object
│   └─ Service Wrapper pattern
│
├─ Has customizable sections
│   └─ Slots pattern
│
└─ Very simple, no template file needed
    └─ Inline Template pattern

Pattern 1: Basic Component

# app/components/ui/badge_component.rb
class Ui::BadgeComponent < ViewComponent::Base
  def initialize(text:, color: :gray)
    @text = text
    @color = color
  end

  # Private methods for internal logic
  private

  def color_classes
    { gray: "bg-gray-100 text-gray-800",
      green: "bg-green-100 text-green-800",
      red: "bg-red-100 text-red-800" }[@color]
  end
end
<%# app/components/ui/badge_component.html.erb %>
<span class="px-2 py-1 text-xs font-medium rounded-full <%= color_classes %>">
  <%= @text %>
</span>

Pattern 2: Service Wrapper

Use when component wraps a service object and needs to expose its data.

class Dashboard::MetricsComponent < ViewComponent::Base
  # EXPOSE all methods view needs (critical!)
  delegate :total_tasks, :completed_tasks, :pending_tasks, to: :@service

  def initialize(service:)
    @service = service
  end

  # Add formatted versions as wrappers
  def formatted_success_rate
    "#{(@service.success_rate * 100).round(1)}%"
  end

  def formatted_currency(amount)
    helpers.number_to_currency(amount)
  end
end

Verification before writing view:

# List methods view will call
grep -oE '@component\.[a-z_]+' app/views/dashboard/*.erb | sort -u

# List public methods in component
grep -E '^\s+def [a-z_]+' app/components/dashboard/metrics_component.rb

# Any mismatch = BUG

Pattern 3: Slots

class Card::Component < ViewComponent::Base
  renders_one :header    # Single slot
  renders_one :footer
  renders_many :actions  # Multiple slots

  def initialize(title: nil, collapsible: false)
    @title = title
    @collapsible = collapsible
  end
end
<%# app/components/card/component.html.erb %>
<div class="bg-white rounded-lg shadow">
  <% if header? || @title %>
    <div class="px-4 py-3 border-b">
      <%= header? ? header : content_tag(:h3, @title, class: "text-lg font-medium") %>
    </div>
  <% end %>

  <div class="p-4">
    <%= content %>
  </div>

  <% if footer? || actions? %>
    <div class="px-4 py-3 border-t flex justify-end space-x-2">
      <%= footer? ? footer : safe_join(actions) %>
    </div>
  <% end %>
</div>

Usage:

<%= render Card::Component.new(title: "Stats") do |card| %>
  <% card.with_header do %>Custom Header<% end %>
  <% card.with_action do %>
    <%= helpers.link_to "Edit", edit_path %>
  <% end %>
  Body content here
<% end %>

Pattern 4: Inline Template

Use for simple components that don't need a separate template file.

class Ui::IconComponent < ViewComponent::Base
  def initialize(name:, size: :md)
    @name = name
    @size = size
  end

  def call
    helpers.content_tag :svg, class: svg_classes do
      helpers.content_tag :use, nil, href: "#icon-#{@name}"
    end
  end

  private

  def svg_classes
    size_class = { sm: "w-4 h-4", md: "w-5 h-5", lg: "w-6 h-6" }[@size]
    "inline-block #{size_class}"
  end
end

Helper Access Rules

ALWAYS use helpers. prefix or delegate:

# WRONG - raises undefined method
def user_link
  link_to(@user.name, user_path(@user))
end

# RIGHT - helpers prefix
def user_link
  helpers.link_to(@user.name, helpers.user_path(@user))
end

# BETTER - delegate for frequently used
class MyComponent < ViewComponent::Base
  delegate :link_to, :image_tag, :number_to_currency, :dom_id, to: :helpers
end

Common helpers needing prefix:

  • Navigation: linkto, buttonto, urlfor, *path
  • Assets: imagetag, assetpath
  • Formatting: numbertocurrency, timeagoin_words, truncate
  • HTML: contenttag, tag, safejoin, dom_id

NEVER Do This

NEVER assume service methods are accessible from view:

<%# WRONG - view reaches through component %>
<%= @dashboard.service.calculate_total %>

<%# RIGHT - component exposes method %>
<%= @dashboard.total %>

NEVER create component without template (unless using def call):

# This will error with "template not found"
class MyComponent < ViewComponent::Base
  def initialize(data:)
    @data = data
  end
  # Missing: template file OR def call
end

NEVER use Rails helpers without helpers. prefix:

# WRONG
link_to("Click", path)  # undefined method

# RIGHT
helpers.link_to("Click", path)

Testing Components

RSpec.describe Dashboard::MetricsComponent, type: :component do
  let(:service) { instance_double(MetricsService) }

  before do
    allow(service).to receive(:total_tasks).and_return(100)
    allow(service).to receive(:success_rate).and_return(0.85)
  end

  it "renders total tasks" do
    render_inline(described_class.new(service: service))
    expect(page).to have_text("100")
  end

  it "formats success rate" do
    component = described_class.new(service: service)
    expect(component.formatted_success_rate).to eq("85.0%")
  end
end

Pre-Creation Checklist

[ ] Checked existing component patterns in codebase
[ ] Determined template style (file vs inline def call)
[ ] Listed ALL methods view will need
[ ] All needed methods are PUBLIC on component
[ ] Service methods exposed via delegate or wrappers
[ ] All Rails helpers use helpers. prefix or are delegated

Handoff Template

When completing component work:

## Component: Namespace::NameComponent
- File: app/components/namespace/name_component.rb
- Template: app/components/namespace/name_component.html.erb

### Public Methods (callable from view)
- method_name → ReturnType
- other_method(param) → ReturnType

### Usage
<%= render Namespace::NameComponent.new(service: @service) %>

### Verified
- [ ] Template renders
- [ ] All view-needed methods exposed
- [ ] helpers. prefix used correctly