Back to list
radioactive-labs

plutonium-views

by radioactive-labs

Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, AI-ready.

52🍴 7📅 Jan 23, 2026

SKILL.md


name: plutonium-views description: Customizing Plutonium views - pages, forms, displays, tables, and layouts using Phlex

Plutonium Views

Plutonium uses Phlex for all view components. This provides a Ruby-first approach to building HTML with full IDE support and type safety.

Architecture Overview

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

Each definition has nested classes you can override:

class PostDefinition < ResourceDefinition
  class IndexPage < IndexPage; end
  class ShowPage < ShowPage; end
  class NewPage < NewPage; end
  class EditPage < EditPage; end
  class Form < Form; end
  class Table < Table; end
  class Display < Display; end
end

Page Customization

Page Configuration

Set page titles and descriptions in definitions:

class PostDefinition < ResourceDefinition
  # Static 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"

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

Custom Page Class

Override page rendering by subclassing:

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

Custom View Files

For complete control, create custom ERB view files that replace the default entirely.

File locations:

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

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

Default view structure:

The default views simply render the page class:

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

Custom view example:

<%# app/views/posts/show.html.erb %>
<div class="max-w-4xl mx-auto">
  <article class="prose lg:prose-xl">
    <h1><%= resource_record!.title %></h1>
    <div class="meta text-gray-500">
      By <%= resource_record!.author.name %> on <%= resource_record!.created_at.strftime("%B %d, %Y") %>
    </div>
    <%= 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>

Mixing approaches:

Render the default page with additions:

<%# app/views/posts/show.html.erb %>
<div class="announcement-banner">
  Special announcement here
</div>

<%= render current_definition.show_page_class.new %>

<div class="related-posts">
  <%= render partial: "related_posts", locals: { post: resource_record! } %>
</div>

Form Customization

Custom Form Template

Override how fields are rendered:

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
recordThe form object (alias: object)
resource_fieldsList of permitted field names
resource_definitionThe definition instance

Display Customization

Custom Display Template

Override the show page detail rendering:

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

Custom Table Rendering

Override list page table:

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:

class MyPage < Plutonium::UI::Page::Base
  def render_content
    # 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:

  • Breadcrumbs()
  • PageHeader(title:, description:, actions:)
  • Panel(**attrs)
  • Block(**attrs)
  • TabList(items:)
  • EmptyCard(message)
  • ActionButton(action, url:)
  • DynaFrameHost() / DynaFrameContent()
  • TableSearchBar()
  • TableScopesBar()
  • TableInfo(pagy)
  • TablePagination(pagy)

Custom Components

Creating a Phlex Component

# 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 in Definitions

Reference components in field definitions:

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

Eject Layout

Copy the layout template to your project:

rails generate pu:eject:layout

This copies layouts/resource.html.erb to your portal.

Custom Layout Class

Override the Phlex layout:

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

    # Custom body classes
    def body_attributes
      {class: "antialiased bg-slate-100 dark:bg-slate-900"}
    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_before_contentInside main, before content
render_after_contentInside main, after content
render_flashFlash messages
render_headHTML head section
render_titlePage title tag
render_metatagsMeta tags
render_assetsCSS/JS assets
render_body_scriptsScripts at end of body

Available Context

Both ERB views and Phlex components have access to the same 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

Authentication

MethodDescription
current_userAuthenticated user (if using Rodauth)

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

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

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

In ERB Views

<%# All methods available directly %>
<%= resource_record!.title %>
<%= current_user.name %>
<%= link_to "Edit", resource_url_for(resource_record!, action: :edit) %>

<%# Render Phlex components %>
<%= render current_definition.show_page_class.new %>
<%= render MyCustomComponent.new(post: resource_record!) %>

Portal-Specific Views

Each portal can have its own view overrides:

# 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
  • plutonium-forms - Custom form templates and field builders
  • plutonium-assets - TailwindCSS and component theming
  • plutonium-definition-fields - Field/input/display configuration
  • plutonium-definition-actions - Action buttons and interactions
  • plutonium-controller - Presentation hooks (present_parent?, etc.)
  • plutonium-portal - Portal-specific customization

Score

Total Score

75/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

+10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon