frontend-patterns
Frontend Patterns
Build Rails frontend using Slim templates, Simple Form, Stimulus controllers, and Optics CSS.
Tech Stack: Slim (HTML) • Simple Form (forms) • Stimulus (JavaScript) • Optics (CSS)
See references/EXAMPLES.md for detailed code examples.
Slim Templates
Core Conventions
- Use Ruby 3+ syntax ( e.g. keyword arguments with
:) - Keep view logic minimal - extract to helpers/partials
- Always add policy checks around actions (e.g. edit/delete links)
- Never use inline styles
- Extract repeated markup into partials (DRY principle)
- Always use locals with keyword arguments:
render 'partial', user:, active: true
Helpers vs Partials
Use Helpers for:
- Single elements with conditional text/classes
- Data formatting (dates, currency)
- Stateless logic
- Example:
status_badge(status)
Use Partials for:
- Multi-element structures
- Reusable UI components
- Collection rendering
- Example:
_form.html.slim,_user_card.html.slim
Rule: Single element = helper. Structure/layout = partial.
Partial Organization
app/views/
resource_name/
index.html.slim # Main views
_form.html.slim # Forms (shared by new/edit)
_resource_name.html.slim # Individual item
shared/
_status_badge.html.slim # Cross-feature components
Common Patterns
-# Conditional classes
.card class=class_names('card--active': active, 'card--urgent': urgent)
-# Partial with locals
= render 'user_card', user:, show_actions: true
-# Collection rendering
= render partial: 'item', collection: @items
-# Conditional rendering
- if policy(@resource).update?
= link_to 'Edit', edit_path(@resource)
Simple Form
Always use Simple Form. Never use form_with or form_for.
Basic Forms
Model form:
= simple_form_for @user do |f|
= f.input :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 (search, filters, bulk actions):
= simple_form_for :search, url: search_path, method: :get do |f|
= f.input :query
= f.submit 'Search', class: 'btn btn--primary'
Important: :symbol forms nest params: params.dig(:search, :query)
Common Input Types
| Type | Example |
|---|---|
| Text | = f.input :name |
| Textarea | = f.input :description, as: :text, input_html: { rows: 4 } |
| Association | = f.association :project, collection: @projects, prompt: 'Select...' |
| Select | = f.input :type, collection: @project_types, prompt: 'Select...' |
| Boolean | = f.input :active, as: :boolean |
| Date | = f.input :start_date, as: :date |
| Hidden | = f.hidden_field :organization_id, value: current_user.organization_id |
Input Options
placeholder:- Placeholder textlabel:- Custom labelhint:- Help text below inputrequired: true- Mark as requireddisabled: true- Disable inputinput_html: {}- HTML attributes for input elementwrapper_html: {}- HTML attributes for wrapper div
Collections
/ Basic collection
= f.input :category_id, collection: @categories
/ Custom label/value methods
= f.input :project_id,
collection: @projects,
label_method: :name,
value_method: :id,
prompt: 'Select project...'
Special Form Patterns
Bulk action form:
= simple_form_for :bulk_action, url: bulk_path, method: :post, html: { id: 'bulk-form' } do |f|
= f.button 'Approve Selected', class: 'btn btn--primary'
-# Checkboxes reference form
= check_box_tag 'entry_ids[]', entry.id, false, form: 'bulk-form'
Modal form with external submit:
= 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
= button_tag 'Submit', type: 'submit', form: 'modal-form', class: 'btn btn--primary'
Form Options
html: {}- HTML attributes for form elementdata: {}- Data attributes (e.g., Turbo, Stimulus)url:- Form submission URL (required for:symbolforms)method:- HTTP method (:get,:post,:patch,:delete)
See references/EXAMPLES.md for complex form examples.
Stimulus Controllers
JavaScript interactions using Stimulus framework.
Controller Structure
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["output", "input"]
static values = { url: String, delay: Number }
static classes = ["hidden", "active"]
connect() {
// Initialization when controller connects to DOM
}
disconnect() {
// Cleanup when controller disconnects
}
action(event) {
// Action methods called from HTML
}
}
Usage in Slim
.component data-controller="example" data-example-url-value="/api/endpoint"
input data-example-target="input" data-action="input->example#search"
.results data-example-target="output"
Best Practices
- One controller per behavior (focused, composable)
- Use data attributes for configuration
- Name controllers in kebab-case in HTML
- Keep controllers simple and testable
- Clean up in
disconnect()(timers, listeners)
See references/EXAMPLES.md for complete controller examples.
CSS & Optics
Guidelines
- Keep custom CSS minimal and component-scoped
- Never use inline styles
- Use of utility classes is strongly discouraged. BEM (Block Element Modifier) structure should be used instead.
- Use
class_nameshelper for conditional classes
Common Patterns
/ Custom component with BEM
.time-entry.time-entry--running
.time-entry__header
h3.time-entry__title = entry.description
.time-entry__body
span.time-entry__duration = entry.duration
Conditional Classes
/ Using class_names helper
.card class=class_names(
'card--active': @record.active?,
'card--featured': @record.featured?
)
Quick Reference
Form actions pattern:
.form__actions
= link_to 'Cancel', :back, class: 'btn btn--outline'
= f.submit 'Save', class: 'btn btn--primary'
Empty state:
- if @items.empty?
= render 'shared/empty_state', title: 'No items', message: 'Create your first item.'
Authorization check:
- if policy(@resource).update?
= link_to 'Edit', edit_path(@resource), class: 'btn'
Turbo confirmation:
= button_to 'Delete', path, method: :delete, data: { turbo_confirm: 'Are you sure?' }
More from rolemodel/rolemodel-skills
bem-structure
Expert guidance for writing, refactoring, and structuring CSS using BEM (Block Element Modifier) methodology. Provides proper CSS class naming conventions, component structure, and Optics design system integration for maintainable, scalable stylesheets.
81optics-context
Use the Optics design framework for styling applications. Apply Optics classes for layout, spacing, typography, colors, and components. Use when working on CSS, styling views, or implementing design system guidelines.
37routing-patterns
Review, generate, and update Rails routes following professional patterns and best practices. Covers RESTful resource routing, route concerns for code reusability, shallow nesting strategies, and advanced route configurations.
28turbo-fetch
Implement dynamic form updates using Turbo Streams and Stimulus. Use when forms need to update fields based on user selections without full page reloads, such as cascading dropdowns, conditional fields, or dynamic option lists.
27stimulus-controllers
Create and register Stimulus controllers for interactive JavaScript features. Use when adding client-side interactivity, dynamic UI updates, or when the user mentions Stimulus controllers or JavaScript behavior.
26controller-patterns
Review and update existing Rails controllers and generate new controllers following professional patterns and best practices. Covers RESTful conventions, authorization patterns, proper error handling, and maintainable code organization.
26