frontend-patterns

Frontend patterns for Rails applications using Slim templates, Stimulus JavaScript framework, CSS with Optics utilities. Use when building views, adding interactivity, styling components, or when the user mentions Slim, Stimulus, JavaScript, CSS, or frontend development.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "frontend-patterns" with this command: npx skills add rolemodel/rolemodel-skills/rolemodel-rolemodel-skills-frontend-patterns

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 new and edit pages into _form partials
  • 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, never form_with or form_for
  • Use :symbol for non-model forms with url parameter
  • Use @model for model-based forms
  • Leverage Simple Form's automatic label generation
  • Use input_html for custom HTML attributes on the input element
  • Use html option for attributes on the form element itself
  • Keep forms accessible with proper labels
  • Use .form__actions for 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

bem-structure

No summary provided by upstream source.

Repository SourceNeeds Review
General

optics-context

No summary provided by upstream source.

Repository SourceNeeds Review
General

routing-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

turbo-fetch

No summary provided by upstream source.

Repository SourceNeeds Review