Skip to content

Forms Reference

Complete reference for form customization using Phlexi::Form.

Overview

Plutonium forms are built on Phlexi::Form, providing a Ruby-first approach to form building.

Form Class Hierarchy

Phlexi::Form::Base
└── Plutonium::UI::Form::Base       # Base with Plutonium components
    ├── Plutonium::UI::Form::Resource   # Resource CRUD forms
    │   └── Plutonium::UI::Form::Interaction  # Interactive action forms
    └── Plutonium::UI::Form::Query      # Search/filter forms

Custom Form Template

Override form rendering in your definition:

ruby
class PostDefinition < ResourceDefinition
  class Form < Form
    def form_template
      # Your custom layout
      render_fields
      render_actions
    end
  end
end

Form Template Methods

MethodDescription
form_templateMain template method to override
render_fieldsRender all permitted fields
render_resource_field(name)Render a single field by name
render_actionsRender submit buttons
fields_wrapper { }Wrapper div for field grid
actions_wrapper { }Wrapper div for buttons

Form Attributes

AttributeDescription
object / recordThe form object being edited
resource_fieldsArray of permitted field names
resource_definitionThe definition instance

Custom Layouts

Sectioned Form

ruby
class PostDefinition < ResourceDefinition
  class Form < Form
    def form_template
      section("Basic Information") {
        render_resource_field :title
        render_resource_field :slug
      }

      section("Content") {
        render_resource_field :content
        render_resource_field :excerpt
      }

      render_actions
    end

    private

    def section(title, &)
      div(class: "mb-8") {
        h3(class: "text-lg font-semibold mb-4") { title }
        fields_wrapper(&)
      }
    end
  end
end

Two-Column Layout

ruby
class Form < Form
  def form_template
    div(class: "grid grid-cols-1 lg:grid-cols-3 gap-6") {
      # Main content - 2 columns
      div(class: "lg:col-span-2") {
        fields_wrapper {
          render_resource_field :title
          render_resource_field :content
        }
      }

      # Sidebar - 1 column
      div(class: "space-y-4") {
        Panel {
          h4(class: "font-medium mb-2") { "Settings" }
          render_resource_field :status
          render_resource_field :visibility
        }
      }
    }

    render_actions
  end
end

Field Builder

When using render_resource_field, Plutonium applies definition configuration. For custom rendering, use the field method directly.

Basic Field Usage

ruby
def form_template
  # Using field builder directly
  render field(:title).wrapped { |f| f.input_tag }
  render field(:content).wrapped { |f| f.easymde_tag }
  render field(:published).wrapped { |f| f.checkbox_tag }

  render_actions
end

Standard Tag Methods

MethodInput Type
f.input_tagText input (auto-detects type)
f.string_tagText input
f.text_tagTextarea
f.number_tagNumber input
f.email_tagEmail input
f.password_tagPassword input
f.url_tagURL input
f.phone_tagTelephone input
f.hidden_tagHidden input
f.date_tagDate input
f.time_tagTime input
f.datetime_tagDatetime input
f.checkbox_tagCheckbox
f.boolean_tagCheckbox (themed as boolean)
f.select_tagSelect dropdown
f.radio_button_tagRadio button
f.collection_radio_buttons_tagRadio button collection
f.collection_checkboxes_tagCheckbox collection
f.range_tagRange slider
f.file_input_tagFile input

Plutonium-Enhanced Tags

Plutonium extends the form builder with additional tags. See the Form::Base::Builder source for the current list.

Common ones include:

  • f.easymde_tag / f.markdown_tag - Markdown editor
  • f.slim_select_tag - Enhanced select
  • f.flatpickr_tag - Date/time picker
  • f.uppy_tag / f.file_tag - File upload
  • f.secure_association_tag - Association with SGID

Field with Options

ruby
# Select with choices
render field(:status).wrapped { |f|
  f.select_tag(choices: %w[draft published archived])
}

# Date picker with options
render field(:published_at).wrapped { |f|
  f.flatpickr_tag(min_date: Date.today, enable_time: true)
}

# File upload with restrictions
render field(:avatar).wrapped { |f|
  f.uppy_tag(
    allowed_file_types: %w[.jpg .png .gif],
    max_file_size: 5.megabytes
  )
}

Wrapped vs Unwrapped

ruby
# Wrapped - includes label, hint, errors
render field(:title).wrapped { |f| f.input_tag }

# Unwrapped - just the input element
render field(:title).input_tag

# Custom wrapper options
render field(:title).wrapped(class: "col-span-full") { |f|
  f.input_tag
}

Input Configuration in Definitions

Define inputs in the definition, render them in the form:

ruby
class PostDefinition < ResourceDefinition
  # Configure inputs
  input :title, hint: "Be descriptive", placeholder: "Enter title"
  input :content, as: :markdown
  input :status, as: :select, choices: %w[draft published]
  input :published_at, as: :flatpickr

  # Custom input with block
  input :category do |f|
    choices = Category.active.pluck(:name, :id)
    f.select_tag(choices: choices)
  end

  class Form < Form
    def form_template
      # render_resource_field uses the input configuration
      render_resource_field :title
      render_resource_field :content
      render_resource_field :status
      render_resource_field :published_at
      render_resource_field :category

      render_actions
    end
  end
end

Nested Forms

For has_many / has_one associations with accepts_nested_attributes_for:

Model Setup

ruby
class Post < ResourceRecord
  has_many :comments
  accepts_nested_attributes_for :comments, allow_destroy: true
end

Definition Setup

ruby
class PostDefinition < ResourceDefinition
  nested_input :comments do |n|
    n.input :author_name
    n.input :body, as: :text
  end

  # Or reference another definition
  nested_input :comments, using: CommentDefinition, fields: %i[author_name body]
end

Rendering

ruby
class Form < Form
  def form_template
    render_resource_field :title
    render_resource_field :content

    # Nested fields are automatically handled
    render_resource_field :comments

    render_actions
  end
end

Dynamic Forms (pre_submit)

Fields with pre_submit: true trigger form re-rendering on change:

ruby
class PostDefinition < ResourceDefinition
  input :post_type, as: :select,
    choices: %w[article video podcast],
    pre_submit: true

  input :video_url,
    condition: -> { object.post_type == "video" }

  input :podcast_url,
    condition: -> { object.post_type == "podcast" }
end

When post_type changes, the form re-renders via Turbo and shows/hides conditional fields.

Form Actions

Default Actions

ruby
def render_actions
  actions_wrapper {
    render submit_button
  }
end

Custom Actions

ruby
def render_actions
  actions_wrapper {
    # Cancel link
    a(href: resource_url_for(resource_class), class: "btn btn-secondary") {
      "Cancel"
    }

    # Save as draft
    button(type: :submit, name: "draft", value: "1", class: "btn") {
      "Save Draft"
    }

    # Primary submit
    render submit_button
  }
end

Form Context

Inside form templates:

ruby
class Form < Form
  def form_template
    # Form object
    object              # The record
    record              # Alias for object
    object.new_record?  # Check if creating

    # Request context
    current_user
    current_parent
    request
    params

    # Definition
    resource_definition
    resource_fields     # Permitted fields

    # URL helpers
    resource_url_for(object)
    resource_url_for(Post, action: :new)

    # Rails helpers
    helpers.link_to(...)
  end
end

Interaction Forms

Forms for interactive actions:

ruby
class PublishPostInteraction < ResourceInteraction
  attribute :publish_date, :date
  attribute :notify_subscribers, :boolean, default: true

  input :publish_date, as: :flatpickr
  input :notify_subscribers

  # Custom form (optional)
  class Form < Plutonium::UI::Form::Interaction
    def form_template
      div(class: "space-y-4") {
        render_resource_field :publish_date
        render_resource_field :notify_subscribers
      }
      render_actions
    end
  end
end

Released under the MIT License.