frontend-patterns
9
总安装量
9
周安装量
#32413
全站排名
安装命令
npx skills add https://github.com/rolemodel/rolemodel-skills --skill frontend-patterns
Agent 安装分布
github-copilot
9
opencode
8
gemini-cli
8
codex
8
kimi-cli
8
amp
8
Skill 文档
Frontend Patterns
Tech Stack
- Slim – HTML templating
- Stimulus – JavaScript interactions
- CSS – Styling
- Optics – CSS styling framework
- Simple Form – Form builder
Slim Templates
Conventions
- Use Ruby 3+ syntax (e.g., keyword arguments with
:) - Keep logic minimal in views
- Extract complex rendering to helpers or partials
- Use locals for partial data passing
- Always prioritize DRY principles – extract repeated markup into partials
- Extract partials when logic or markup is repeated more than once
- Never use inline styles
When to Use Helpers vs Partials
Use Helper Methods when:
- Simple conditional logic that returns HTML with different text/classes
- Formatting data (dates, currency, durations)
- Generating single HTML elements with varying attributes
- Logic is stateless and doesn’t need multiple elements
- Example:
status_badge(status),format_duration(seconds)
Use Partials when:
- Complex markup structure (multiple nested elements)
- Reusable UI components with layout
- Need to render collections
- Significant HTML that would clutter a helper
- Example:
_time_entry_row.html.slim,_timer_form.html.slim
Rule of thumb: If it’s primarily conditional text/classes in a single element, use a helper. If it’s a structure/layout, use a partial.
Partial Extraction Guidelines
- Extract forms on the
newandeditpages into_formpartials - Extract repeated structures into component partials
- Use descriptive partial names:
_time_entry_row,_project_selector,_status_badge - Place partials in same directory as parent view or in
shared/for cross-feature use - Always use keyword arguments for partial locals:
render 'row', time_entry:, show_actions: true
Partial Organization
app/views/
time_entries/
edit.html.slim # Edit view
index.html.slim # Main view
new.html.slim # New view
show.html.slim # Show view
_time_entries.html.slim # Table collection of rows
_time_entry.html.slim # Individual row
_form.html.slim # Time Entries form
shared/
_status_badge.html.slim # Reusable badge
_empty_state.html.slim # Empty state pattern
Conditional class names
Use the rails class_names helper to manage conditional class names in Slim templates.
button.btn class=class_names('btn--active': active) Click Me
Example
-# locals: (user:, active: false)
.user-card class=('active' if active)
h3 = user.name
p = user.email
Simple Form
Overview
Always use Simple Form for forms. Never use form_with, form_for, or Rails form helpers directly.
Basic Model Form
= simple_form_for @user do |f|
= f.input :first_name
= f.input :last_name
= f.input :email, required: true
.form__actions
= link_to 'Cancel', :back, class: 'btn btn--outline'
= f.submit 'Save', class: 'btn btn--primary'
Non-Model Form (with URL)
For forms without a model (like bulk actions or search forms):
= simple_form_for :search, url: search_path, method: :get do |f|
= f.input :query, label: 'Search'
= f.submit 'Search', class: 'btn btn--primary'
Important: When using simple_form_for :symbol, params are nested under the symbol:
# View: simple_form_for :time_entry
# Params received: { time_entry: { task_id: 1, description: "text" } }
# Access with: params.dig(:time_entry, :task_id)
Form with HTML Options
= simple_form_for @record, html: { id: 'custom-form', class: 'special-form' } do |f|
= f.input :name
Form with Data Attributes (Turbo)
= simple_form_for @record, data: { turbo_frame: '_top' } do |f|
= f.input :name
Common Input Types
/ Text input
= f.input :name
/ Text area
= f.input :description, as: :text, input_html: { rows: 4 }
/ Select dropdown
= f.input :project_id, collection: @projects, prompt: 'Select a project...'
/ Boolean checkbox
= f.input :active, as: :boolean
/ Date picker
= f.input :start_date, as: :date
/ With placeholder
= f.input :email, placeholder: 'user@example.com'
/ With custom input attributes
= f.input :description, input_html: { rows: 3, required: true, data: { controller: 'auto-save' } }
Collections and Associations
/ Simple collection
= f.input :category_id, collection: @categories
/ With custom text/value methods
= f.input :project_id, collection: @projects, label_method: :name, value_method: :id
/ Grouped collection
= f.input :task_id, as: :grouped_select, collection: @projects, group_method: :tasks
/ With prompt
= f.input :status, collection: ['pending', 'approved', 'rejected'], prompt: 'Select status...'
Custom Labels and Hints
= f.input :email, label: 'Email Address', hint: 'We will never share your email'
= f.input :password, label: 'Password', placeholder: 'At least 8 characters'
Disabled Inputs
= f.input :task_id, disabled: true, input_html: { data: { target: 'form.taskSelect' } }
Hidden Fields
= f.hidden_field :organization_id, value: current_user.organization_id
Submit Buttons
/ Standard submit
= f.submit 'Save', class: 'btn btn--primary'
/ With data attributes
= f.submit 'Save', class: 'btn btn--primary', data: { disable_with: 'Saving...' }
/ Associated with external form (for modal footers)
= f.submit 'Save', form: 'my-form-id', class: 'btn btn--primary'
Form Actions Pattern
Standard pattern for form button groups:
.form__actions
= link_to 'Cancel', :back, class: 'btn btn--outline'
= f.submit 'Save', class: 'btn btn--primary'
Bulk Action Forms
For forms that collect checkboxes without inputs:
= simple_form_for :bulk_action, url: bulk_approve_path, method: :post, html: { id: 'bulk-form' } do |f|
/ Form will collect checked checkboxes via form attribute
/ Checkboxes reference the form
= check_box_tag 'entry_ids[]', entry.id, false, form: 'bulk-form'
Modal Forms
Forms that submit within modals and redirect to parent page:
= simple_form_for @record, html: { id: 'modal-form' }, data: { turbo_frame: '_top' } do |f|
= f.input :reason, as: :text, input_html: { rows: 3, required: true }
-# In modal footer (outside form)
- content_for :modal_actions do
= button_tag 'Submit', type: 'submit', form: 'modal-form', class: 'btn btn--primary'
Best Practices
- Always use
simple_form_for, neverform_withorform_for - Use
:symbolfor non-model forms with url parameter - Use
@modelfor model-based forms - Leverage Simple Form’s automatic label generation
- Use
input_htmlfor custom HTML attributes on the input element - Use
htmloption for attributes on the form element itself - Keep forms accessible with proper labels
- Use
.form__actionsfor button groups
Stimulus Controllers
Structure
- One controller per behavior
- Use data attributes for configuration
- Keep controllers focused and composable
- Follow naming conventions (kebab-case in HTML)
Example
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["output"]
static values = { url: String }
connect() {
// Initialization
}
perform() {
// Action logic
}
}
CSS & Optics
Guidelines
- Use Optics utility classes where applicable
- Keep custom CSS minimal and scoped
- Follow BEM or similar naming for custom components
- Avoid inline styles
Future Topics
- Turbo Frames and Streams patterns
- Form styling conventions
- Icon helper usage
- Responsive design patterns
- Animation and transition guidelines