mcpField
R
rails-components

rails-components

github.com/thinkoodleデザイン・UI7175 回インストール8 週間前に更新
リポジトリ

rails-components について

rails-componentsは、CSSクラス、partial、helper、ViewComponentを使った再利用可能なRails UIコンポーネントの構築を導くスキルです。適切で簡潔な実装方法を選び、構造や規約を統一するのに役立ちます。

主な機能

  1. 01CSSからViewComponentまでの選択ガイド
  2. 02再利用可能なpartialとhelperのパターン
  3. 03コンポーネントの命名・ファイル規約
  4. 04スロットとバリアントの実装指針
  5. 05コンポーネントギャラリーの設定

活用シーン

  1. 01新しいUIコンポーネントの実装方法の選択
  2. 02重複したRailsマークアップのリファクタリング
  3. 03共有コンポーネントのスタイルとテンプレートの整理
  4. 04開発用コンポーネントギャラリーの作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thinkoodle/rails-skills --skill rails-components

インストール後、Claude Code で/rails-componentsを入力して読み込まれたか確認します。

同梱ファイルreference.md

詳細

Rails コンポーネントのパターン

Rails のプリミティブであるパーシャル、CSS クラス、ヘルパーを使って、再利用可能で自己完結型かつ組み合わせ可能な UI コンポーネントを構築します。

考え方

基本原則:

  1. まず CSS クラスから始める — クラスで済む場合はパーシャルを使わないでください
  2. 自己完結型 — 各コンポーネントは、マークアップとスタイルを同じ場所で管理します
  3. 組み合わせ可能 — コンポーネントは自然に入れ子にしたり組み合わせたりできます
  4. 一貫性 — 同じコンポーネントなら、同じマークアップと表示になります
  5. 段階的に複雑化する — 必要になるまでは、CSS → パーシャル → ヘルパーの順に進みます

コンポーネントの段階:

ViewComponent (gem)     ← Complex: encapsulated Ruby + template + tests
Helper method           ← Frequent: shorthand for common partials
Partial with locals     ← Structured: logic, slots, complex markup
CSS classes only        ← Simple: single element, few variants

必ず一番下から始めてください。不便を感じたときにだけ上の段階へ進みます。

このスキルを使う場面

  • 再利用可能な新しい UI コンポーネント(ボタン、カード、モーダルなど)の構築
  • CSS クラス、パーシャル、ヘルパー、ViewComponent のどれを使うか判断するとき
  • 空状態、アラート、バッジ、データテーブルの作成
  • 繰り返されるマークアップを共通コンポーネントにリファクタリング
  • 開発用コンポーネントギャラリーの設定
  • バリエーションのパターン(サイズ、色、状態)の実装
  • コンポーネントのファイルやスタイルの整理

判断の基準

CSS クラスを使う場合:
  • コンポーネントが単一の要素、または単純なラッパー(<button>、<span>、<div>)である
  • バリエーションが純粋に見た目に関するもの(色、サイズ、余白)である
  • 条件分岐が不要である
  • 内側のテキスト以外にコンテンツスロットがない

例: ボタン、バッジ、シンプルなカード、フォーム入力、区切り線

パーシャルを使う場合:
  • 条件付きのセクションがある(引数に応じて表示/非表示を切り替える)
  • 名前付きのコンテンツ領域が複数ある(ヘッダー、本文、フッター)
  • マークアップの構造が単純ではない(要素が 5 個以上)
  • デフォルト値や計算された属性が必要である

例: 空状態、モーダル、複雑なカード、データテーブル、アイコン付きアラート

ヘルパーを使う場合:
  • パーシャルが非常に頻繁に使われる(呼び出し箇所が 10 か所以上)
  • より簡潔な Ruby API を使いたい:ui_badge("Active", variant: :success)
  • ERB の render 構文が重く感じられるシンプルなコンポーネント
  • 複数の要素を Ruby で組み合わせたい

例: バッジ、ステータスインジケーター、アイコンボタン、パンくずリスト

ViewComponent を使う場合:
  • コンポーネント自体の単体テストが必要である
  • ビューに置くべきではない複雑な状態ロジックがある
  • チームの規模が大きく、コンポーネントに厳密なインターフェースが必要である
  • 数十個のコンポーネントを備えたデザインシステムを構築する

ほとんどの Rails アプリでは ViewComponent を使わないでください。 パーシャルとヘルパーでニーズの 90% をカバーできます。

手順

ステップ 1:既存のコンポーネントを確認する

必ず最初に既存のパターンを探してください:

# Find existing component partials
find app/views -path "*/components/*" -o -path "*/_component*" | head -20

# Find component CSS
find app/assets -name "*component*" -o -path "*/components/*" | head -20

# Find component helpers
rg "def ui_|def component_" app/helpers/

# Check for ViewComponent usage
ls app/components/ 2>/dev/null
rg "ViewComponent" Gemfile

既存プロジェクトの規約に合わせてください。 自分の好みより一貫性を優先します。

ステップ 2:適切な方法を選ぶ

上記の判断基準を参考にしてください。次の点を確認します:

  1. CSS クラスだけで実現できますか? → その方法を使います。
  2. 条件分岐やスロットが必要ですか? → パーシャルを使います。
  3. 同じパターンで 10 か所以上から呼び出されますか? → ヘルパーを追加します。
  4. 独立したテストと複雑な状態が必要ですか? → ViewComponent を使います。
ステップ 3:ファイル構成
app/views/components/          # Shared component partials
├── _modal.html.erb
├── _empty_state.html.erb
├── _card.html.erb
├── _alert.html.erb
└── _data_table.html.erb

app/assets/stylesheets/components/   # One CSS file per component
├── buttons.css
├── badges.css
├── cards.css
├── modals.css
├── empty-states.css
├── alerts.css
└── tables.css

app/helpers/
└── component_helper.rb        # Helper shortcuts for common components

命名規則:

  • パーシャル:app/views/components/ 内に _snake_case.html.erb
  • CSS ファイル:app/assets/stylesheets/components/ 内に kebab-case.css
  • ヘルパー:ComponentHelper 内で ui_component_name をプレフィックスとして使う
  • CSS クラス:.component-name、.component-name-element、.component-name--variant
ステップ 4:CSS のみでコンポーネントを構築する

シンプルなコンポーネントでは、CSS クラスを定義してテンプレートで直接使います:

/* app/assets/stylesheets/components/buttons.css */
@layer components {
  .btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    border-radius: 0.375rem;
    font-weight: 500;
    cursor: pointer;
    transition: background-color 0.15s;
  }

  .btn-primary { background: var(--color-primary); color: white; }
  .btn-secondary { background: var(--color-surface); border: 1px solid var(--color-border); }
  .btn-danger { background: var(--color-danger); color: white; }
  .btn-ghost { background: transparent; }
  .btn-sm { padding: 0.25rem 0.75rem; font-size: 0.875rem; }
  .btn-lg { padding: 0.75rem 1.5rem; }
  .btn-icon { padding: 0.5rem; }
}
<%# Usage — no partial needed %>
<button class="btn btn-primary">Save</button>
<button class="btn btn-danger btn-sm">Delete</button>
<%= link_to "View", post_path(@post), class: "btn btn-secondary" %>
ステップ 5:パーシャルベースのコンポーネントを構築する

すべてのパーシャルには、インターフェースを記述するコメントブロックを必ず付けてください:

<%# app/views/components/_empty_state.html.erb %>
<%#
  Empty State Component

  Arguments:
    icon:        (String) Icon name — required
    title:       (String) Main heading — required
    description: (String, optional) Supporting text
    class:       (String, optional) Additional CSS classes

  Block: Optional action buttons
%>
<div class="empty-state <%= local_assigns[:class] %>">
  <div class="empty-state-icon">
    <%= lucide_icon icon, size: 48 %>
  </div>

  <h3 class="empty-state-title"><%= title %></h3>

  <% if local_assigns[:description] %>
    <p class="empty-state-description"><%= description %></p>
  <% end %>

  <% if block_given? %>
    <div class="empty-state-actions">
      <%= yield %>
    </div>
  <% end %>
</div>

パーシャルに関する主なルール:

  1. オプション引数の確認には local_assigns[:key] を使います — defined? や if key は使いません
  2. 引数にフォールバック値が必要な場合は、デフォルト値に ||= を使います
  3. ブロックが渡されたかどうかの確認には block_given? を使います
  4. 主要なコンテンツスロットには yield を使います
  5. 追加の名前付きスロットには capture を使います
ステップ 6:capture でスロットを実装する

コンテンツ領域が複数あるコンポーネントの場合:

<%# app/views/components/_card.html.erb %>
<%#
  Card Component

  Arguments:
    header:  (String|HTML, optional) Header content — use capture for complex HTML
    footer:  (String|HTML, optional) Footer content — use capture for complex HTML
    class:   (String, optional) Additional CSS classes
    variant: (Symbol, optional) Card variant — :default, :stat, :interactive

  Block: Card body content (required)
%>
<%
  variant ||= :default
  css_class = ["card", ("card-#{variant}" unless variant == :default), local_assigns[:class]].compact.join(" ")
%>
<div class="<%= css_class %>">
  <% if local_assigns[:header] %>
    <div class="card-header"><%= header %></div>
  <% end %>

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

  <% if local_assigns[:footer] %>
    <div class="card-footer"><%= footer %></div>
  <% end %>
</div>

呼び出し側では、リッチなスロットコンテンツに capture を使います:

<%= render "components/card",
    header: capture { tag.h3("Settings", class: "card-title") },
    footer: capture {
      link_to("Cancel", "#", class: "btn btn-secondary") +
      link_to("Save", "#", class: "btn btn-primary")
    } do %>
  <p>Card body content here.</p>
<% end %>
ステップ 7:ヘルパーショートカットを追加する

頻繁に使用するコンポーネントには、ヘルパーを作成します。

# app/helpers/component_helper.rb
module ComponentHelper
  def ui_badge(text, variant: :muted)
    tag.span(text, class: "badge badge-#{variant}")
  end

  def ui_empty_state(title:, icon: nil, description: nil, &block)
    render("components/empty_state",
      title: title,
      icon: icon,
      description: description,
      &block)
  end

  def ui_alert(variant: :info, dismissible: false, &block)
    render("components/alert",
      variant: variant,
      dismissible: dismissible,
      &block)
  end
end

次のように簡潔に記述できます:

<%= ui_badge "Active", variant: :success %>
<%= ui_badge "Draft" %>

<%= ui_empty_state(
  icon: "inbox",
  title: "No messages",
  description: "Check back later") %>
ステップ 8:バリエーションを扱う

見た目のみを変更する場合は、CSS のバリエーションを使用します。

/* Size variants */
.btn-sm { padding: 0.25rem 0.75rem; font-size: 0.875rem; }
.btn-lg { padding: 0.75rem 1.5rem; }

/* State variants */
.card-interactive:hover { border-color: var(--color-border-strong); }
.card-flush .card-body { padding: 0; }

マークアップが変わる場合は、引数の一部を変えたバリエーションを使用します。

<%# In the partial %>
<%
  variant ||= :info
  icon_name = { success: "check-circle", error: "alert-circle",
                warning: "alert-triangle", info: "info" }[variant.to_sym]
%>
<div class="alert alert-<%= variant %>" role="alert">
  <%= lucide_icon icon_name, size: 18 %>
  <span class="alert-content"><%= yield %></span>
</div>
ステップ 9:コンポーネントギャラリーを作成する

開発に不可欠です。 誰でもすべてのコンポーネントを閲覧できるようにします。

# config/routes.rb
if Rails.env.development?
  get "components", to: "components#index"
end
# app/controllers/components_controller.rb
class ComponentsController < ApplicationController
  layout "application"

  def index
  end
end
<%# app/views/components/index.html.erb %>
<div style="max-width: 48rem; margin: 2rem auto; padding: 0 1rem;">
  <h1>Component Gallery</h1>

  <h2>Buttons</h2>
  <div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 2rem;">
    <button class="btn btn-primary">Primary</button>
    <button class="btn btn-secondary">Secondary</button>
    <button class="btn btn-danger">Danger</button>
    <button class="btn btn-ghost">Ghost</button>
    <button class="btn btn-sm">Small</button>
  </div>

  <h2>Badges</h2>
  <div style="display: flex; gap: 0.5rem; margin-bottom: 2rem;">
    <span class="badge badge-muted">Draft</span>
    <span class="badge badge-success">Active</span>
    <span class="badge badge-warning">Pending</span>
    <span class="badge badge-danger">Failed</span>
  </div>

  <h2>Empty States</h2>
  <%= render "components/empty_state",
      icon: "inbox",
      title: "No messages yet",
      description: "When you receive messages, they'll appear here." do %>
    <button class="btn btn-primary">Compose</button>
  <% end %>

  <h2>Alerts</h2>
  <%= render "components/alert", variant: :info do %>Info message<% end %>
  <%= render "components/alert", variant: :success do %>Success message<% end %>
  <%= render "components/alert", variant: :warning do %>Warning message<% end %>
  <%= render "components/alert", variant: :error do %>Error message<% end %>
</div>

アンチパターン

  1. 何にでもパーシャルを使う — <span class="badge"> に render "components/badge" は不要です。CSS クラスで十分です。
  2. defined?(variable) を使う — パーシャルでは常に local_assigns[:variable] を使います。defined? は信頼できません。
  3. コンポーネント固有のスタイルにグローバルスタイルを使う — コンポーネントの CSS は components/ に配置します。application.css を汚さないでください。
  4. 命名に一貫性がない — 規則を決め、それに従います。_empty_state.html.erb を使い、_emptyState.html.erb は使いません。
  5. 巨大なコンポーネント — パーシャルが 10 個以上の引数を取る場合は、より小さなコンポーネントに分割します。
  6. ドキュメントがない — すべてのパーシャルに、引数を列挙したコメントブロックが必要です。
  7. HTML 文字列を引数として渡す — capture { } ブロックまたは yield を使い、生の HTML 文字列は使わないでください。
  8. ギャラリーページがない — すべてのコンポーネントを一か所で確認できないと、内容にばらつきが生じます。

CSS の規約

/* Use @layer for specificity management */
@layer components {
  .component-name { /* base styles */ }
  .component-name-element { /* child element */ }
  .component-name--variant { /* BEM variant — OR use component-variant */ }
}

デザイントークンの使用:

.component {
  color: var(--color-ink);           /* Text colors */
  background: var(--color-surface);  /* Backgrounds */
  border: 1px solid var(--color-border);  /* Borders */
  padding: var(--space-4);           /* Spacing scale */
  font-size: var(--text-sm);         /* Type scale */
  border-radius: var(--radius-md);   /* Border radius */
}

プロジェクトでデザイントークン/CSS カスタムプロパティを使用している場合は、それらを使います。使用していない場合は、一貫性のある生の値を使います。

クイックリファレンス

local_assigns チートシート
<%# Check if argument was passed (even if nil) %>
<% if local_assigns.key?(:title) %>

<%# Check if argument was passed AND is truthy %>
<% if local_assigns[:title] %>

<%# Default value %>
<% variant = local_assigns[:variant] || :default %>
<%# OR %>
<% variant ||= :default %>

<%# Pass-through CSS classes %>
<div class="component <%= local_assigns[:class] %>">
レンダリング構文
<%# Simple — no block %>
<%= render "components/empty_state", icon: "inbox", title: "Empty" %>

<%# With block %>
<%= render "components/modal", title: "Confirm" do %>
  <p>Are you sure?</p>
<% end %>

<%# With capture slots %>
<%= render "components/card",
    header: capture { tag.h3("Title") },
    footer: capture { tag.button("Save", class: "btn btn-primary") } do %>
  <p>Body</p>
<% end %>
一般的なコンポーネントのテンプレート

完全なテンプレートについては、このスキルディレクトリ内の reference.md を参照してください。

  • ボタン、バッジ(CSS のみ)
  • カード、空状態、アラート(パーシャル)
  • Stimulus を使ったモーダル(パーシャル+JS)
  • データテーブル(パーシャル)
  • 上記すべてのヘルパーメソッド

コメント