This skill should be used when the user asks about "views", "ERB templates", "partials", "layouts", "render", "form_with", "form helpers", "view helpers", "link_to", "content_for", "yield", "collection rendering", "locals", "tag helpers", "asset helpers", "image_tag", "stylesheet_link_tag", or needs guidance on building Rails views and templates.
Provides guidance on building Rails views, templates, partials, layouts, and using view helpers.
npx claudepluginhub bastos/ruby-plugin-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/form-patterns.mdreferences/view-helpers.mdComprehensive guide to Rails views, templates, partials, layouts, and view helpers.
<%# Comment - not rendered %>
<% code %> <%# Execute Ruby, no output %>
<%= expression %> <%# Output result (escaped) %>
<%== raw_html %> <%# Output without escaping (use carefully) %>
<%- code -%> <%# Suppress leading/trailing whitespace %>
<%# Conditionals %>
<% if @user.admin? %>
<span class="badge">Admin</span>
<% end %>
<%# Iteration %>
<% @articles.each do |article| %>
<article>
<h2><%= article.title %></h2>
<p><%= truncate(article.body, length: 100) %></p>
</article>
<% end %>
<%# Safe output %>
<%= sanitize(@article.body) %>
<%= raw(@trusted_html) %>
Partials are reusable view fragments. Filename starts with underscore.
<%# app/views/articles/_article.html.erb %>
<article id="<%= dom_id(article) %>">
<h2><%= article.title %></h2>
<p><%= article.body %></p>
</article>
<%# Render partial %>
<%= render "article", article: @article %>
<%= render partial: "article", locals: { article: @article } %>
<%# Renders _article.html.erb for each item %>
<%= render @articles %>
<%# Explicit collection %>
<%= render partial: "article", collection: @articles %>
<%# With spacer %>
<%= render partial: "article", collection: @articles, spacer_template: "article_divider" %>
<%# Cached collection %>
<%= render partial: "article", collection: @articles, cached: true %>
<%# Counter and iteration info available %>
<%# article_counter (0-indexed) %>
<%# article_iteration.first?, .last?, .index, .size %>
<%# Strict locals (Rails 7.1+) %>
<%# locals: (article:, show_author: true) -%>
<article>
<h2><%= article.title %></h2>
<% if show_author %>
<p>By <%= article.author.name %></p>
<% end %>
</article>
<%# Wrap partial in a layout %>
<%= render partial: "article", layout: "card", locals: { article: @article } %>
<%# app/views/shared/_card.html.erb %>
<div class="card">
<%= yield %>
</div>
<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "My App" %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<%= yield :head %>
</head>
<body>
<%= render "shared/header" %>
<% if notice %>
<div class="notice"><%= notice %></div>
<% end %>
<% if alert %>
<div class="alert"><%= alert %></div>
<% end %>
<main>
<%= yield %>
</main>
<%= render "shared/footer" %>
</body>
</html>
<%# In view %>
<% content_for :title, "My Page Title" %>
<% content_for :head do %>
<%= stylesheet_link_tag "custom" %>
<% end %>
<% content_for :sidebar do %>
<nav>Sidebar content</nav>
<% end %>
<%# In layout %>
<%= content_for?(:sidebar) ? yield(:sidebar) : render("default_sidebar") %>
class AdminController < ApplicationController
layout "admin"
end
class ArticlesController < ApplicationController
layout "article", only: [:show]
layout :determine_layout
private
def determine_layout
current_user&.admin? ? "admin" : "application"
end
end
<%# Model-backed form %>
<%= form_with model: @article do |form| %>
<%= form.label :title %>
<%= form.text_field :title %>
<%= form.label :body %>
<%= form.text_area :body, rows: 10 %>
<%= form.label :status %>
<%= form.select :status, Article::STATUSES %>
<%= form.submit %>
<% end %>
<%# URL-based form %>
<%= form_with url: search_path, method: :get do |form| %>
<%= form.text_field :q, placeholder: "Search..." %>
<%= form.submit "Search" %>
<% end %>
<%# Text inputs %>
<%= form.text_field :name %>
<%= form.email_field :email %>
<%= form.password_field :password %>
<%= form.telephone_field :phone %>
<%= form.url_field :website %>
<%= form.number_field :quantity, min: 1, max: 100 %>
<%= form.range_field :rating, min: 1, max: 5 %>
<%= form.search_field :q %>
<%= form.text_area :description, rows: 5 %>
<%# Hidden %>
<%= form.hidden_field :status, value: "draft" %>
<%# Checkboxes and radios %>
<%= form.check_box :published %>
<%= form.radio_button :status, "draft" %>
<%= form.radio_button :status, "published" %>
<%# Select %>
<%= form.select :category_id, Category.pluck(:name, :id) %>
<%= form.select :role, %w[admin editor viewer], { include_blank: "Select role" } %>
<%# Collection select %>
<%= form.collection_select :category_id, Category.all, :id, :name %>
<%= form.collection_radio_buttons :status, Status.all, :id, :name %>
<%= form.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
<%# Date/time %>
<%= form.date_field :published_on %>
<%= form.time_field :start_time %>
<%= form.datetime_local_field :event_at %>
<%= form.date_select :birthday %>
<%= form.time_zone_select :time_zone %>
<%# File upload %>
<%= form.file_field :avatar %>
<%= form.file_field :images, multiple: true %>
# Model
class Article < ApplicationRecord
has_many :comments
accepts_nested_attributes_for :comments, allow_destroy: true
end
<%= form_with model: @article do |form| %>
<%= form.text_field :title %>
<%= form.fields_for :comments do |comment_form| %>
<%= comment_form.text_area :body %>
<%= comment_form.check_box :_destroy %>
<%= comment_form.label :_destroy, "Delete" %>
<% end %>
<%= form.submit %>
<% end %>
<%# Links %>
<%= link_to "Home", root_path %>
<%= link_to "Article", @article %>
<%= link_to "Edit", edit_article_path(@article) %>
<%= link_to "Delete", @article, method: :delete, data: { turbo_method: :delete, turbo_confirm: "Sure?" } %>
<%# With block %>
<%= link_to article_path(@article) do %>
<strong><%= @article.title %></strong>
<% end %>
<%# Button (creates form) %>
<%= button_to "Subscribe", subscriptions_path, method: :post %>
<%# Email %>
<%= mail_to "support@example.com" %>
<%= mail_to "support@example.com", "Contact Us", subject: "Help Request" %>
<%# Current page check %>
<%= link_to "Home", root_path, class: ("active" if current_page?(root_path)) %>
<%# Modern tag helper syntax %>
<%= tag.div class: "container" do %>
<%= tag.h1 @article.title %>
<%= tag.p @article.body, class: "content" %>
<% end %>
<%# With data attributes %>
<%= tag.div data: { controller: "toggle", action: "click->toggle#switch" } do %>
Content
<% end %>
<%# Self-closing tags %>
<%= tag.br %>
<%= tag.hr %>
<%= tag.input type: "text", name: "query" %>
<%# class_names / token_list %>
<%= tag.div class: class_names("base", active: @active, hidden: @hidden) %>
<%# Images %>
<%= image_tag "logo.png" %>
<%= image_tag "logo.png", alt: "Logo", class: "logo", size: "100x50" %>
<%= image_tag @user.avatar, fallback: "default_avatar.png" %>
<%# Stylesheets %>
<%= stylesheet_link_tag "application", media: "all" %>
<%= stylesheet_link_tag "print", media: "print" %>
<%# JavaScript %>
<%= javascript_include_tag "application" %>
<%= javascript_importmap_tags %>
<%# Favicon %>
<%= favicon_link_tag "favicon.ico" %>
<%# Video/Audio %>
<%= video_tag "intro.mp4", controls: true, autoplay: false %>
<%= audio_tag "podcast.mp3", controls: true %>
<%# Truncate %>
<%= truncate(@article.body, length: 100) %>
<%= truncate(@article.body, length: 100, omission: "... (more)") %>
<%# Excerpt %>
<%= excerpt(@article.body, "Rails", radius: 25) %>
<%# Pluralize %>
<%= pluralize(@comments.count, "comment") %>
<%# Word wrap %>
<%= word_wrap(@text, line_width: 80) %>
<%# Simple format (converts newlines to <br> and paragraphs) %>
<%= simple_format(@article.body) %>
<%# Highlight %>
<%= highlight(@article.body, "Rails") %>
<%= number_to_currency(1234.56) %> <%# $1,234.56 %>
<%= number_to_currency(1234.56, unit: "€") %> <%# €1,234.56 %>
<%= number_to_percentage(75.5) %> <%# 75.500% %>
<%= number_to_percentage(75.5, precision: 0) %> <%# 76% %>
<%= number_with_delimiter(12345678) %> <%# 12,345,678 %>
<%= number_with_precision(111.2345, precision: 2) %> <%# 111.23 %>
<%= number_to_human(1234567) %> <%# 1.23 Million %>
<%= number_to_human_size(1234567) %> <%# 1.18 MB %>
<%= number_to_phone(5551234567) %> <%# 555-123-4567 %>
<%= distance_of_time_in_words(Time.current, 3.days.from_now) %> <%# about 3 days %>
<%= time_ago_in_words(@article.created_at) %> <%# 2 hours ago %>
<%# Remove dangerous HTML %>
<%= sanitize(@user_input) %>
<%# Allow specific tags %>
<%= sanitize(@content, tags: %w[p br strong em]) %>
<%# Strip all tags %>
<%= strip_tags(@html_content) %>
<%# Strip links but keep text %>
<%= strip_links(@content_with_links) %>
references/form-patterns.md - Complex form patterns, custom form buildersreferences/view-helpers.md - Creating custom helpers, helper organizationActivates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.