Skip to content

Views Reference

Complete reference for UI customization with Phlex components.

Overview

Plutonium uses Phlex for all view components. Customization happens through:

  • Definition nested classes (pages, forms, tables, displays)
  • Page hooks for injecting content
  • Custom Phlex components
  • ERB view overrides

Architecture

Definition
├── IndexPage    → renders Table
├── ShowPage     → renders Display
├── NewPage      → renders Form
├── EditPage     → renders Form
└── InteractiveActionPage → renders Interaction Form

Page Configuration

Set titles and descriptions in definitions:

ruby
class PostDefinition < ResourceDefinition
  # Page titles
  index_page_title "Blog Posts"
  index_page_description "Manage all published articles"

  show_page_title "Article Details"
  new_page_title "Write New Article"
  edit_page_title "Edit Article"

  # Breadcrumbs
  breadcrumbs true              # Global default
  index_page_breadcrumbs false  # Per-page override
  show_page_breadcrumbs true
end

Custom Page Classes

Override page rendering by defining nested classes in your definition:

ruby
class PostDefinition < ResourceDefinition
  class ShowPage < ShowPage
    private

    # Custom title logic
    def page_title
      "#{object.title} - #{object.author.name}"
    end

    # Add content before the main area
    def render_before_content
      div(class: "alert alert-info") {
        "This post has #{object.comments.count} comments"
      }
    end

    # Add content after
    def render_after_content
      render RelatedPostsComponent.new(post: object)
    end

    # Override the toolbar
    def render_toolbar
      div(class: "flex gap-2") {
        button(class: "btn") { "Preview" }
        button(class: "btn btn-primary") { "Publish" }
      }
    end
  end
end

Page Hooks

All pages inherit these customization hooks:

HookPurpose
render_before_headerBefore entire header section
render_after_headerAfter entire header section
render_before_breadcrumbsBefore breadcrumbs
render_after_breadcrumbsAfter breadcrumbs
render_before_page_headerBefore title/actions
render_after_page_headerAfter title/actions
render_before_toolbarBefore toolbar
render_after_toolbarAfter toolbar
render_before_contentBefore main content
render_after_contentAfter main content
render_before_footerBefore footer
render_after_footerAfter footer

Form Customization

Override form rendering in your definition:

ruby
class PostDefinition < ResourceDefinition
  class Form < Form
    def form_template
      # Custom layout with sections
      div(class: "grid grid-cols-2 gap-6") {
        div {
          h3(class: "text-lg font-medium") { "Basic Info" }
          render_resource_field :title
          render_resource_field :slug
        }

        div {
          h3(class: "text-lg font-medium") { "Content" }
          render_resource_field :content
        }
      }

      div(class: "mt-6") {
        h3(class: "text-lg font-medium") { "Publishing" }
        render_resource_field :published_at
        render_resource_field :category
      }

      render_actions
    end
  end
end

Form Methods

MethodPurpose
render_fieldsRender all permitted fields
render_resource_field(name)Render a single field
render_actionsRender submit buttons
record / objectThe form object
resource_fieldsList of permitted field names
resource_definitionThe definition instance

Display Customization

Override show page detail rendering:

ruby
class PostDefinition < ResourceDefinition
  class Display < Display
    def display_template
      # Hero section
      div(class: "bg-gradient-to-r from-blue-500 to-purple-600 p-8 rounded-lg text-white mb-6") {
        h1(class: "text-3xl font-bold") { object.title }
        p(class: "mt-2 opacity-90") { object.excerpt }
      }

      # Main content
      Block do
        fields_wrapper do
          render_resource_field :author
          render_resource_field :published_at
          render_resource_field :category
        end
      end

      # Full-width content
      Block do
        div(class: "prose max-w-none") {
          raw object.content
        }
      end

      # Associations (tabs)
      render_associations if present_associations?
    end
  end
end

Display Methods

MethodPurpose
render_fieldsRender all permitted fields in a block
render_resource_field(name)Render single field
render_associationsRender association tabs
objectThe record being displayed
resource_fieldsList of permitted field names
resource_associationsList of permitted associations

Table Customization

Override index page table:

ruby
class PostDefinition < ResourceDefinition
  class Table < Table
    def view_template
      render_search_bar
      render_scopes_bar

      if collection.empty?
        render_empty_card
      else
        # Custom card grid instead of table
        div(class: "grid grid-cols-3 gap-4") {
          collection.each do |post|
            render PostCardComponent.new(post:)
          end
        }
      end

      render_footer
    end
  end
end

Table Methods

MethodPurpose
render_search_barSearch input
render_scopes_barScope tabs
render_tableDefault table
render_empty_cardEmpty state
render_footerPagination
collectionThe paginated records
resource_fieldsColumn field names

Component Kit

Plutonium provides shorthand methods for common components:

ruby
class MyPage < Plutonium::UI::Page::Base
  def view_template
    # These are automatically rendered
    PageHeader(title: "Dashboard")

    Panel(class: "mt-4") {
      p { "Content here" }
    }

    Block {
      TabList(items: tabs)
    }

    EmptyCard("No items found")

    ActionButton(action, url: "/posts/new")
  end
end

Available kit methods:

MethodPurpose
Breadcrumbs()Navigation breadcrumbs
PageHeader(title:, description:, actions:)Page header with actions
Panel(**attrs)Content panel
Block(**attrs)Content block
TabList(items:)Tab navigation
EmptyCard(message)Empty state card
ActionButton(action, url:)Action button
DynaFrameHost() / DynaFrameContent()Turbo frame helpers
TableSearchBar()Search bar for tables
TableScopesBar()Scope tabs for tables
TableInfo(pagy)Pagination info
TablePagination(pagy)Pagination links
FrameNavigatorPanel(title:, src:, panel_id:)Frame navigation panel

Custom Components

Creating a Phlex Component

ruby
# app/components/post_card_component.rb
class PostCardComponent < Plutonium::UI::Component::Base
  def initialize(post:)
    @post = post
  end

  def view_template
    div(class: "bg-white rounded-lg shadow p-4") {
      h3(class: "font-bold") { @post.title }
      p(class: "text-gray-600 mt-2") { @post.excerpt }

      div(class: "mt-4 flex justify-between items-center") {
        span(class: "text-sm text-gray-500") { @post.published_at&.strftime("%B %d, %Y") }
        a(href: resource_url_for(@post), class: "text-blue-600") { "Read more" }
      }
    }
  end
end

Using Components in Definitions

ruby
class PostDefinition < ResourceDefinition
  # Custom display component
  display :status, as: StatusBadgeComponent

  # Custom input component
  input :color, as: ColorPickerComponent

  # Block with component
  display :metrics do |field|
    MetricsChartComponent.new(data: field.value)
  end
end

Layout Customization

Custom Layout Class

ruby
# packages/admin_portal/app/views/layouts/admin_portal/resource_layout.rb
module AdminPortal
  class ResourceLayout < Plutonium::UI::Layout::ResourceLayout
    private

    # Custom main content area classes
    def main_attributes
      mix(super, { class: "pt-20 lg:ml-64" })
    end

    # Add custom header content
    def render_before_main
      super
      render AnnouncementBanner.new if Announcement.active.any?
    end

    # Custom scripts
    def render_body_scripts
      super
      script(src: "/custom-analytics.js")
    end
  end
end

Layout Hooks

HookPurpose
render_before_mainBefore main content area
render_after_mainAfter main (modals, etc.)
render_headHTML head section
render_titlePage title tag
render_assetsCSS/JS assets
render_body_scriptsScripts at end of body

Custom ERB Views

For complete control, create custom ERB view files:

# Main app (for a PostsController)
app/views/posts/index.html.erb
app/views/posts/show.html.erb

# Portal-specific
packages/admin_portal/app/views/admin_portal/posts/show.html.erb

The default views render the page class:

erb
<%# app/views/resource/show.html.erb %>
<%= render current_definition.show_page_class.new %>

Custom view example:

erb
<%# app/views/posts/show.html.erb %>
<div class="max-w-4xl mx-auto">
  <article class="prose lg:prose-xl">
    <h1><%= resource_record!.title %></h1>
    <%= raw resource_record!.content %>
  </article>

  <div class="mt-8">
    <%= link_to "Edit", resource_url_for(resource_record!, action: :edit), class: "btn" %>
    <%= link_to "Back", resource_url_for(Post), class: "btn" %>
  </div>
</div>

Available Context

Resource Methods

MethodDescription
resource_classThe model class (e.g., Post)
resource_record!Current record (raises if not found)
resource_record?Current record (nil if not found)
current_parentParent record for nested routes
current_scoped_entityEntity for multi-tenant portals

Definition & Policy

MethodDescription
current_definitionDefinition instance for current resource
current_policyPolicy instance for current record
current_authorized_scopeScoped collection user can access

URL Helpers

MethodDescription
resource_url_for(record)URL for a record
resource_url_for(record, action: :edit)Action URL for record
resource_url_for(Model)Index URL for model
resource_url_for(Model, action: :new)New URL for model
resource_url_for(record, parent: parent)Nested resource URL

Display Helpers

MethodDescription
display_name_of(record)Human-readable name for record
resource_name(klass)Singular model name
resource_name_plural(klass)Plural model name

In Phlex Components

ruby
class MyComponent < Plutonium::UI::Component::Base
  def view_template
    # Plutonium methods work directly
    current_user
    resource_record!
    resource_url_for(@post)

    # Rails helpers via helpers proxy
    helpers.link_to(...)
    helpers.image_tag(...)
  end
end

Portal-Specific Views

Each portal can override views:

ruby
# Base definition
class PostDefinition < ResourceDefinition
  class ShowPage < ShowPage
    # Default behavior
  end
end

# Admin portal override
class AdminPortal::PostDefinition < ::PostDefinition
  class ShowPage < ShowPage  # Inherits from ::PostDefinition::ShowPage
    def render_after_content
      super
      render AdminOnlySection.new(post: object)
    end
  end
end

Released under the MIT License.