Skip to main content

Your First Component

Let’s build a simple HTML component to understand Phlex’s core concepts.
1

Create a component class

Every Phlex component inherits from Phlex::HTML and defines a view_template method:
class GreetingComponent < Phlex::HTML
  def view_template
    h1 { "Hello, World!" }
  end
end
The view_template method is where you define your HTML structure using Phlex’s DSL.
2

Render the component

Call your component to generate HTML:
html = GreetingComponent.call
puts html
# => <h1>Hello, World!</h1>
Use .call on the class to instantiate and render in one step.

Adding Dynamic Content

Components accept data through their initializer:
class GreetingComponent < Phlex::HTML
  def initialize(name:)
    @name = name
  end

  def view_template
    h1 { "Hello, #{@name}!" }
  end
end

GreetingComponent.call(name: "Ruby")
# => <h1>Hello, Ruby!</h1>
Phlex automatically escapes text content to prevent XSS attacks. Raw HTML requires explicit opt-in using raw(safe("<html>")).

Working with Attributes

Phlex provides a clean syntax for HTML attributes:
class ButtonComponent < Phlex::HTML
  def view_template
    button(
      class: "btn btn-primary",
      id: "submit-button",
      disabled: true
    ) { "Submit" }
  end
end

# => <button class="btn btn-primary" id="submit-button" disabled>Submit</button>

Attribute Type Reference

Use true to include the attribute, false or nil to omit it:
input(disabled: true, checked: false)
# => <input disabled>
Both strings and symbols work. Symbols with underscores convert to dashes:
div(class: :text_center)  # => <div class="text-center"></div>
div(class: "text-center") # => <div class="text-center"></div>
Integers and floats are automatically converted to strings:
input(type: "number", value: 42, step: 0.5)
# => <input type="number" value="42" step="0.5">
Hashes create nested attributes joined with dashes:
div(data: { user_id: 123, role: "admin" })
# => <div data-user-id="123" data-role="admin"></div>

Composing Components

Build complex UIs by composing components together:
class LayoutComponent < Phlex::HTML
  def initialize(title:)
    @title = title
  end

  def view_template(&block)
    html do
      head { title { @title } }
      body(&block)
    end
  end
end

class ArticleComponent < Phlex::HTML
  def initialize(title:, content:)
    @title = title
    @content = content
  end

  def view_template
    article do
      h1 { @title }
      p { @content }
    end
  end
end

class PageComponent < Phlex::HTML
  def view_template
    render LayoutComponent.new(title: "My Blog") do
      render ArticleComponent.new(
        title: "Getting Started with Phlex",
        content: "Phlex makes building views enjoyable!"
      )
    end
  end
end

PageComponent.call
Use render to include one component inside another. Pass blocks to parent components for flexible layouts.

Complete Example: Card Component

Here’s a practical example combining everything we’ve learned:
class CardComponent < Phlex::HTML
  def initialize(title:, description:, image_url: nil, featured: false)
    @title = title
    @description = description
    @image_url = image_url
    @featured = featured
  end

  def view_template
    article(class: card_classes) do
      render_image if @image_url
      
      div(class: "p-6") do
        h2(class: "text-2xl font-bold mb-2") { @title }
        p(class: "text-gray-600") { @description }
        
        render_badge if @featured
      end
    end
  end

  private

  def card_classes
    classes = ["rounded-lg", "shadow-lg", "overflow-hidden"]
    classes << "border-2 border-yellow-400" if @featured
    classes
  end

  def render_image
    img(
      src: @image_url,
      alt: @title,
      class: "w-full h-48 object-cover"
    )
  end

  def render_badge
    span(
      class: "inline-block px-3 py-1 bg-yellow-400 text-sm font-semibold rounded mt-4"
    ) { "Featured" }
  end
end

# Usage
CardComponent.call(
  title: "Phlex Basics",
  description: "Learn the fundamentals of component-based views",
  image_url: "/images/phlex-basics.jpg",
  featured: true
)
Break down complex components into private helper methods. This keeps your code organized and testable.

Inline HTML Without Classes

For quick HTML generation, use Phlex.html without creating a class:
html_string = Phlex.html do
  div(class: "container") do
    h1 { "Quick HTML" }
    p { "No class definition needed" }
  end
end

puts html_string
# => <div class="container"><h1>Quick HTML</h1><p>No class definition needed</p></div>
This is perfect for scripts, tests, or one-off HTML generation tasks.

Plain Text Output

Use plain to output text content explicitly:
class MessageComponent < Phlex::HTML
  def view_template
    div do
      plain "This is "
      strong { "important" }
      plain " text."
    end
  end
end

# => <div>This is <strong>important</strong> text.</div>

Common Patterns

Use standard Ruby conditionals:
def view_template
  if @user.admin?
    button { "Admin Panel" }
  else
    p { "Welcome, #{@user.name}" }
  end
end
Use Ruby’s enumerable methods:
def view_template
  ul do
    @items.each do |item|
      li { item.name }
    end
  end
end
Set defaults in the initializer:
def initialize(title:, subtitle: nil)
  @title = title
  @subtitle = subtitle || "No subtitle provided"
end
Use the tag method for dynamic element types:
def view_template
  @tag_name = :h1
  tag(@tag_name, class: "title") { "Dynamic heading" }
end
# => <h1 class="title">Dynamic heading</h1>

Next Steps

You now know the fundamentals of Phlex! Here’s what to explore next:

Advanced Components

Learn about component inheritance, mixins, and Kit modules

SVG Support

Build scalable vector graphics with the same elegant syntax

Testing Views

Write unit tests for your components

Performance

Optimize rendering with caching and compilation
Join the Phlex community to see real-world examples and get help from other developers.

Build docs developers (and LLMs) love